Skip to content

Conversation

@cardoso
Copy link
Member

@cardoso cardoso commented Nov 18, 2025

Proposed changes (including videos or screenshots)

The Presence.updateConnection function is called when a heartbeat is received from a connection.

Connections with an _updatedAt field older than 5 minutes are considered stale and removed periodically.

Issue(s)

https://rocketchat.atlassian.net/browse/SUP-846

Steps to test or reproduce

Further comments

The majority of changed lines reported are just splitting unit tests and independent functions on the presence service.

Summary by CodeRabbit

  • New Features

    • Background cleanup that removes stale sessions and refreshes presence for affected users
    • Presence now updates more reliably on heartbeat and connection activity
  • Bug Fixes

    • Resolved user status inaccuracy by refreshing active connections and filtering out stale ones
  • Tests

    • Added comprehensive tests for presence and automated session cleanup logic

✏️ Tip: You can customize this high-level summary in your review settings.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Nov 18, 2025

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

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Nov 18, 2025

🦋 Changeset detected

Latest commit: 5633225

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@rocket.chat/meteor Patch
@rocket.chat/core-services Patch
@rocket.chat/model-typings Patch
@rocket.chat/models Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/presence Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/stream-hub-service Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/omnichannel-services Patch
rocketchat-services Patch
@rocket.chat/apps Patch
@rocket.chat/cron Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch
@rocket.chat/media-calls Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/ddp-client Patch
@rocket.chat/freeswitch Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-client Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/license Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch
@rocket.chat/ui-voip Patch

Not sure what this means? Click here to learn what changesets are.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Walkthrough

Introduces a PresenceReaper that periodically removes stale user-session connections, adds heartbeat typing and a Heartbeat class definition, integrates heartbeat-driven presence refreshes, and exposes a new Presence.updateConnection method to refresh activity for specific connections.

Changes

Cohort / File(s) Summary
Type Definitions
apps/meteor/definition/externals/meteor/meteor.d.ts, apps/meteor/definition/externals/meteor/ddp-common.d.ts
Add DDPCommon.Heartbeat types/class and options; replace server: any with a structured server type including sessions map and publish_handlers; update imports to include DDPCommon.
Presence Core & API
ee/packages/presence/src/Presence.ts, packages/core-services/src/types/IPresence.ts
Integrate PresenceReaper into Presence lifecycle (start/stop); add private handleReaperUpdates() and public updateConnection(uid, connectionId) method; add private reaper field; export updateConnection on IPresence.
Presence Reaper Implementation & Tests
ee/packages/presence/src/lib/PresenceReaper.ts, ee/packages/presence/src/lib/PresenceReaper.spec.ts
New PresenceReaper class that scans UsersSessions, builds plans to remove stale connections, performs batched bulkWrite removals, and invokes onUpdate for affected users; includes unit tests covering processing and batching.
DDP Client & Login Hook Integrations
ee/apps/ddp-streamer/src/Client.ts, apps/meteor/ee/server/startup/presence.ts
Add messageReceived hook in DDP Client to report observed packets and call Presence.updateConnection; augment login heartbeat handling to call Presence.updateConnection when appropriate.
Minor / Maintenance
ee/packages/presence/src/lib/processConnectionStatus.ts, .changeset/spicy-nails-design.md
Consolidate imports; add changeset documenting the stale-connection filtering and presence fix.

Sequence Diagram

sequenceDiagram
    participant User as User Session
    participant HB as Heartbeat (DDPCommon.Heartbeat)
    participant Client as DDP Client
    participant Pres as Presence Service
    participant USS as UsersSessions Model
    participant Reaper as PresenceReaper

    rect rgba(200,220,255,0.6)
    Note over User,Client: Active packet observed
    User->>Client: send packet
    Client->>Client: messageReceived() (sets _seenPacket false/true)
    Client->>Pres: updateConnection(uid, connectionId)
    Pres->>USS: updateOne / refresh session timestamp
    USS-->>Pres: ack updated session
    Pres->>Pres: refreshPresence for uid
    end

    rect rgba(220,255,200,0.6)
    Note over Reaper,USS: Periodic cleanup (interval)
    Reaper->>USS: query sessions with connections older than cutoff
    USS-->>Reaper: user docs with connections
    Reaper->>Reaper: processDocument() → build ReaperPlan (removeIds, shouldMarkOffline)
    Reaper->>USS: bulkWrite() remove stale connections
    Reaper->>Pres: onUpdate([userIds]) when users went offline
    Pres->>Pres: handleReaperUpdates() → refresh presence / mark offline
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas needing attention:
    • ee/packages/presence/src/lib/PresenceReaper.ts: cutoff computation, query filters, bulkWrite shape and safety for concurrent updates.
    • ee/packages/presence/src/Presence.ts: integration between reaper callbacks and presence refresh, error handling.
    • ee/apps/ddp-streamer/src/Client.ts and apps/meteor/ee/server/startup/presence.ts: correctness of _seenPacket semantics and timing of updateConnection calls.
    • ee/packages/presence/src/lib/PresenceReaper.spec.ts: test mocks and assertions for bulk operations.

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • scuciatto

Poem

🐰 A hop, a ping, a heartbeat song,
The reaper skips where sessions throng.
Stale tails trimmed, true ones stay bright,
I nibble bugs and guard the night.
Hooray — presence sleeps just right!

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses SUP-846 objectives by implementing presence status refresh on WebSocket pings, filtering stale connections (>5 min old), and preventing dangling sessions from keeping users online.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing dangling sessions: presence reaper mechanism, heartbeat integration, connection status updates, and type definitions for heartbeat handling.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: adding heartbeat-based connection updates and removal of stale connections through the PresenceReaper mechanism.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/presence-dangling

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 and usage tips.

@cardoso cardoso force-pushed the fix/presence-dangling branch from 8c8ebb6 to 3c26ca0 Compare November 18, 2025 18:01
@cardoso cardoso added this to the 7.14.0 milestone Nov 19, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Nov 19, 2025

📦 Docker Image Size Report

📈 Changes

Service Current Baseline Change Percent
sum of all images 1.2GiB 1.2GiB +12MiB
rocketchat 359MiB 347MiB +12MiB
omnichannel-transcript-service 132MiB 132MiB -611B
queue-worker-service 132MiB 132MiB -855B
ddp-streamer-service 126MiB 126MiB +123B
account-service 113MiB 113MiB +252B
stream-hub-service 111MiB 111MiB -455B
presence-service 111MiB 111MiB +7.1KiB
authorization-service 111MiB 111MiB -791B

📊 Historical Trend

---
config:
  theme: "dark"
  xyChart:
    width: 900
    height: 400
---
xychart
  title "Image Size Evolution by Service (Last 30 Days + This PR)"
  x-axis ["11/15 22:28", "11/16 01:28", "11/17 23:50", "11/18 22:53", "11/19 23:02", "11/21 16:49", "11/24 17:34", "11/27 22:32", "11/28 19:05", "12/01 23:01", "12/02 21:57", "12/03 21:00", "12/04 18:17", "12/05 21:56", "12/06 21:43 (PR)"]
  y-axis "Size (GB)" 0 --> 0.5
  line "account-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "authorization-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "ddp-streamer-service" [0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12]
  line "omnichannel-transcript-service" [0.14, 0.14, 0.14, 0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13]
  line "presence-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
  line "queue-worker-service" [0.14, 0.14, 0.14, 0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13]
  line "rocketchat" [0.36, 0.36, 0.35, 0.35, 0.35, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.35]
  line "stream-hub-service" [0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11]
Loading

Statistics (last 14 days):

  • 📊 Average: 1.5GiB
  • ⬇️ Minimum: 1.2GiB
  • ⬆️ Maximum: 1.6GiB
  • 🎯 Current PR: 1.2GiB
ℹ️ About this report

This report compares Docker image sizes from this build against the develop baseline.

  • Tag: pr-37551
  • Baseline: develop
  • Timestamp: 2025-12-06 21:43:07 UTC
  • Historical data points: 14

Updated: Sat, 06 Dec 2025 21:43:08 GMT

@codecov
Copy link

codecov bot commented Nov 19, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.77%. Comparing base (d0be8ad) to head (5633225).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #37551      +/-   ##
===========================================
- Coverage    67.78%   67.77%   -0.01%     
===========================================
  Files         3449     3449              
  Lines       113987   113986       -1     
  Branches     20956    20956              
===========================================
- Hits         77262    77258       -4     
- Misses       34610    34611       +1     
- Partials      2115     2117       +2     
Flag Coverage Δ
e2e 57.24% <ø> (+0.01%) ⬆️
e2e-api 42.13% <ø> (-0.06%) ⬇️

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.

@cardoso cardoso force-pushed the fix/presence-dangling branch from c180d53 to d90e850 Compare November 21, 2025 22:06
@cardoso cardoso marked this pull request as ready for review November 22, 2025 00:28
@cardoso cardoso requested review from a team as code owners November 22, 2025 00:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/models/src/models/UsersSessions.ts (1)

32-41: Optional status handling is good; consider a more explicit check

Making status optional and guarding the $set keeps pings from overwriting the stored status while still refreshing _updatedAt, which matches the intended behavior.

To make this future‑proof against any falsy-but-valid status values, you could prefer an explicit undefined check instead of a truthiness check:

-			$set: {
-				...(status && { 'connections.$.status': status }),
-				'connections.$._updatedAt': new Date(),
-			},
+			$set: {
+				...(status !== undefined && { 'connections.$.status': status }),
+				'connections.$._updatedAt': new Date(),
+			},
apps/meteor/client/meteor/overrides/ddpOverREST.ts (1)

9-29: Fix unsound type predicate and make ping call explicitly fire-and-forget

The shouldBypass type guard (line 9) claims to narrow to non-method messages when it returns true, but it also returns true for several method messages (login/resume, UserPresence:, stream-, and bypassMethods). This violates the type predicate's promise and is unsound.

Additionally, the type narrowing isn't actually being used—the code immediately checks if (message.msg === 'ping') after the predicate succeeds (line 34), indicating the type isn't relied upon.

Change shouldBypass to return a plain boolean:

-const shouldBypass = (message: Meteor.IDDPMessage): message is Exclude<Meteor.IDDPMessage, Meteor.IDDPMethodMessage> => {
+const shouldBypass = (message: Meteor.IDDPMessage): boolean => {
 	if (message.msg !== 'method') {
 		return true;
 	}
 	const { method, params } = message;
 	// ... rest unchanged
}

For the ping side effect (lines 34–36), make the fire-and-forget intent explicit and avoid unhandled rejections:

 if (message.msg === 'ping') {
-	sdk.call('UserPresence:ping');
+	void sdk.call('UserPresence:ping').catch(() => {});
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef03e2 and d90e850.

📒 Files selected for processing (15)
  • apps/meteor/client/meteor/overrides/ddpOverREST.ts (2 hunks)
  • apps/meteor/definition/externals/meteor/meteor.d.ts (1 hunks)
  • apps/meteor/server/methods/userPresence.ts (2 hunks)
  • ee/apps/ddp-streamer/src/DDPStreamer.ts (1 hunks)
  • ee/apps/ddp-streamer/src/configureServer.ts (1 hunks)
  • ee/packages/presence/src/Presence.ts (2 hunks)
  • ee/packages/presence/src/lib/processConnectionStatus.ts (0 hunks)
  • ee/packages/presence/src/lib/processStatus.ts (1 hunks)
  • ee/packages/presence/src/processPresenceAndStatus.ts (1 hunks)
  • ee/packages/presence/tests/lib/processConnectionStatus.test.ts (1 hunks)
  • ee/packages/presence/tests/lib/processPresenceAndStatus.test.ts (1 hunks)
  • ee/packages/presence/tests/lib/processStatus.test.ts (1 hunks)
  • packages/core-services/src/types/IPresence.ts (1 hunks)
  • packages/model-typings/src/models/IUsersSessionsModel.ts (1 hunks)
  • packages/models/src/models/UsersSessions.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • ee/packages/presence/src/lib/processConnectionStatus.ts
🧰 Additional context used
🧬 Code graph analysis (8)
ee/packages/presence/tests/lib/processStatus.test.ts (1)
ee/packages/presence/src/lib/processStatus.ts (1)
  • processStatus (6-16)
ee/packages/presence/src/processPresenceAndStatus.ts (3)
packages/core-typings/src/IUserSession.ts (1)
  • IUserSessionConnection (3-9)
ee/packages/presence/src/lib/processConnectionStatus.ts (1)
  • processConnectionStatus (6-14)
ee/packages/presence/src/lib/processStatus.ts (1)
  • processStatus (6-16)
packages/model-typings/src/models/IUsersSessionsModel.ts (1)
packages/core-typings/src/IUserSession.ts (1)
  • IUserSession (11-14)
ee/apps/ddp-streamer/src/configureServer.ts (2)
ee/packages/presence/src/Presence.ts (1)
  • Presence (11-284)
apps/meteor/server/methods/userPresence.ts (3)
  • userId (24-30)
  • userId (31-37)
  • userId (38-44)
ee/packages/presence/tests/lib/processPresenceAndStatus.test.ts (1)
ee/packages/presence/src/processPresenceAndStatus.ts (1)
  • processPresenceAndStatus (16-31)
packages/models/src/models/UsersSessions.ts (1)
packages/core-typings/src/IUserSession.ts (1)
  • IUserSession (11-14)
ee/apps/ddp-streamer/src/DDPStreamer.ts (3)
ee/apps/ddp-streamer/src/configureServer.ts (4)
  • server (10-10)
  • userId (119-125)
  • userId (126-132)
  • userId (133-139)
apps/meteor/server/methods/userPresence.ts (3)
  • userId (24-30)
  • userId (31-37)
  • userId (38-44)
ee/packages/presence/src/Presence.ts (1)
  • Presence (11-284)
apps/meteor/server/methods/userPresence.ts (2)
ee/packages/presence/src/Presence.ts (1)
  • Presence (11-284)
ee/apps/ddp-streamer/src/configureServer.ts (3)
  • userId (119-125)
  • userId (126-132)
  • userId (133-139)
🔇 Additional comments (13)
packages/model-typings/src/models/IUsersSessionsModel.ts (1)

8-8: LGTM! Optional status enables ping-based connection tracking.

Making the status parameter optional allows the system to update connection timestamps (via ping handlers) without forcing a status change. This helps maintain accurate connection state for presence tracking and addresses the dangling session issue.

apps/meteor/definition/externals/meteor/meteor.d.ts (1)

50-61: LGTM! Improved type safety for DDP messages.

The refactoring separates ping and method messages into distinct interfaces (IDDPPingMessage and IDDPMethodMessage), then creates a union type. This enables type guards and better type narrowing at call sites, improving type safety for DDP message handling.

ee/packages/presence/tests/lib/processStatus.test.ts (1)

1-30: LGTM! Comprehensive test coverage for processStatus.

The test suite thoroughly validates all combinations of connection status and default status across the four UserStatus values (ONLINE, BUSY, AWAY, OFFLINE). The tests confirm the expected precedence logic:

  • OFFLINE connection status always takes precedence
  • ONLINE default allows connection status to pass through
  • Non-ONLINE defaults override the connection status
packages/core-services/src/types/IPresence.ts (1)

18-18: LGTM! Parameter reordering improves API consistency.

The signature change reorders parameters to (uid, session, status?) which is more logical (identifying the connection before specifying its status) and makes status optional. This enables ping handlers to update connection tracking without forcing a status change, which is key to fixing dangling sessions.

Note: This is a breaking change, but the PR appears to update all call sites consistently.

ee/apps/ddp-streamer/src/configureServer.ts (1)

119-139: LGTM! Presence methods updated consistently.

All three presence methods (UserPresence:online, UserPresence:away, and the new UserPresence:ping) have been updated to use the new signature with parameters in the order (userId, session, status?). The new ping method omits the status parameter, allowing it to update connection tracking without changing the user's status, which is crucial for fixing dangling sessions.

ee/packages/presence/src/Presence.ts (2)

7-7: LGTM! Import path updated for refactored module.

The import has been updated to reflect the reorganization of presence utilities, with processPresenceAndStatus now in a dedicated module at the root of the presence package.


206-212: LGTM! Implementation correctly handles optional status.

The setConnectionStatus method now accepts an optional status parameter and passes it through to UsersSessions.updateConnectionStatusById. When status is undefined (as in ping handlers), the connection timestamp is updated without changing the status, which helps maintain accurate connection state while preventing unwanted status changes.

ee/packages/presence/src/lib/processStatus.ts (1)

1-16: LGTM! Status precedence logic is well-designed.

The processStatus function implements a clear precedence hierarchy for determining user status:

  1. OFFLINE connection status always takes precedence - respects actual disconnection
  2. ONLINE default allows connection status to pass through - shows real-time presence
  3. Non-ONLINE defaults override connection status - respects explicit user status settings (BUSY, AWAY)

This logic is intuitive and well-tested in the corresponding test file.

ee/apps/ddp-streamer/src/DDPStreamer.ts (1)

219-225: LGTM, but note the unawaited promise for error handling purposes.

The new PING event handler correctly guards against missing userId and successfully maintains connection state. The concern about concurrent ping events is partially mitigated:

Safe: MongoDB's atomic updateOne with the $ positional operator in updateConnectionStatusById prevents data corruption from concurrent updates to the same user's session document.

⚠️ Consideration: Presence.setConnectionStatus() is not awaited in the PING handler, which means errors in either updateConnectionStatusById or the subsequent updateUserPresence call will be silently swallowed. While updateUserPresence performs a read-compute-write sequence that could theoretically result in stale status being written if multiple calls interleave for the same user, this is acceptable for presence tracking since the system is eventually consistent by design. However, for proper error handling and observability, consider awaiting the call or attaching a .catch() handler to log unexpected failures.

ee/packages/presence/tests/lib/processConnectionStatus.test.ts (1)

4-4: Import path update looks correct

The new relative path to processConnectionStatus matches the tests/libsrc/lib layout and keeps the existing test behavior intact.

ee/packages/presence/src/processPresenceAndStatus.ts (1)

7-31: Presence derivation from fresh sessions looks sound

Filtering by _updatedAt <= 5 minutes, reducing with processConnectionStatus from OFFLINE, and then deriving the final status via processStatus gives the expected behavior for empty, stale-only, and mixed-session inputs and cleanly separates statusConnection from status.

ee/packages/presence/tests/lib/processPresenceAndStatus.test.ts (1)

6-286: Test suite gives comprehensive coverage of the new presence logic

The scenarios here (single vs multiple connections, no connections, stale vs recent updates, and different default statuses) exercise all key branches of processPresenceAndStatus, including the 5‑minute freshness window and the precedence rules between statusConnection and status.

apps/meteor/server/methods/userPresence.ts (1)

12-44: Server-side ping wiring and parameter reordering look consistent

The new 'UserPresence:ping' method and the reordered arguments for Presence.setConnectionStatus(userId, connection.id, status) align with the updated setConnectionStatus(uid, session, status?) signature and allow pings to refresh _updatedAt without mutating status, while still guarding for missing userId/connection.

@cardoso cardoso changed the title fix: dangling usersSessions fix(presence): handle dangling sessions by updating status on ping Nov 22, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
.changeset/spicy-nails-design.md (1)

1-10: Changeset metadata looks good; consider clarifying the summary.

The affected packages and patch version bump are appropriate for this fix. However, the summary could be more specific to help users understand the mechanism. Currently it reads generically; consider mentioning that the fix refreshes connection activity via ping messages and excludes idle sessions.

Example revision:

-Fixes user status inaccuracy by refreshing active connections and filtering out the stale ones.
+Fixes user status inaccuracy by refreshing connection activity on ping and filtering out idle sessions (no activity for 5+ minutes).

This gives future readers clearer context about the fix's scope and timing strategy.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d90e850 and 5f0b3a4.

📒 Files selected for processing (1)
  • .changeset/spicy-nails-design.md (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build

Copy link
Member

@KevLehman KevLehman left a comment

Choose a reason for hiding this comment

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

I think diego has more context on how this should behave, so my comments are on code-side.

I don't see anything spooky on the implementation rather than the fact the connections would be always updated (since we're gonna update the updatedAt on every ping)

@RocketChat RocketChat deleted a comment from coderabbitai bot Nov 27, 2025
@RocketChat RocketChat deleted a comment from coderabbitai bot Nov 27, 2025
@RocketChat RocketChat deleted a comment from coderabbitai bot Nov 27, 2025
@RocketChat RocketChat deleted a comment from coderabbitai bot Nov 27, 2025
@RocketChat RocketChat deleted a comment from coderabbitai bot Nov 27, 2025
@RocketChat RocketChat deleted a comment from coderabbitai bot Nov 27, 2025
@RocketChat RocketChat deleted a comment from coderabbitai bot Nov 27, 2025
@RocketChat RocketChat deleted a comment from coderabbitai bot Nov 27, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 628b508 and a65f79b.

📒 Files selected for processing (3)
  • apps/meteor/ee/server/startup/presence.ts (2 hunks)
  • ee/apps/ddp-streamer/src/DDPStreamer.ts (1 hunks)
  • ee/packages/presence/src/Presence.ts (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ee/apps/ddp-streamer/src/DDPStreamer.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • ee/packages/presence/src/Presence.ts
  • apps/meteor/ee/server/startup/presence.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (6)
apps/meteor/ee/server/startup/presence.ts (1)

32-32: Good type annotation for the login parameter.

The explicit typing clarifies the expected shape of the login object and improves code maintainability.

ee/packages/presence/src/Presence.ts (5)

13-21: Throttle implementation is correct.

The per-connection throttling logic correctly gates status updates to once per 60 seconds, updating the timestamp only when proceeding. Combined with cleanup in removeConnection and removeStaleConnections, this prevents unbounded map growth.


95-98: Verify the stale connection cutoff duration.

The PR description states connections older than "5 minutes" are treated as stale, but CONNECTION_STATUS_UPDATE_INTERVAL is set to 60000ms (1 minute). Confirm whether the 1-minute cutoff is intentional or if it should be 5 minutes (300000ms) as described.

Also applies to: 178-179


258-267: Good design: throttling applies only to heartbeat-driven updates.

When status is explicitly provided (user action), the update proceeds immediately. Throttling only applies to heartbeat pings (no status), which aligns with the PR objective of updating _updatedAt without flooding the database.


111-117: Proper cleanup of timers in stopped().

Both the timeout and interval are correctly cleared, preventing resource leaks when the service is stopped.


165-167: Memory leak addressed: cleanup on connection removal.

The lastConnectionStatusUpdate map entry is properly deleted when a connection is removed, preventing unbounded map growth.

Comment on lines 43 to 47
const _messageReceived = session.heartbeat.messageReceived.bind(session.heartbeat);
session.heartbeat.messageReceived = function messageReceived() {
void Presence.setConnectionStatus(login.user._id, login.connection.id);
return _messageReceived();
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing null check for session.heartbeat.

The code assumes session.heartbeat exists, but if it's undefined (e.g., for non-WebSocket transports or during session initialization), this will throw a TypeError when accessing .messageReceived.

+		if (!session.heartbeat) {
+			return;
+		}
+
 		const _messageReceived = session.heartbeat.messageReceived.bind(session.heartbeat);
 		session.heartbeat.messageReceived = function messageReceived() {
 			void Presence.setConnectionStatus(login.user._id, login.connection.id);
 			return _messageReceived();
 		};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const _messageReceived = session.heartbeat.messageReceived.bind(session.heartbeat);
session.heartbeat.messageReceived = function messageReceived() {
void Presence.setConnectionStatus(login.user._id, login.connection.id);
return _messageReceived();
};
if (!session.heartbeat) {
return;
}
const _messageReceived = session.heartbeat.messageReceived.bind(session.heartbeat);
session.heartbeat.messageReceived = function messageReceived() {
void Presence.setConnectionStatus(login.user._id, login.connection.id);
return _messageReceived();
};
🤖 Prompt for AI Agents
In apps/meteor/ee/server/startup/presence.ts around lines 43 to 47, the code
assumes session.heartbeat exists and directly binds messageReceived which can
throw if heartbeat is undefined; guard access by checking that session.heartbeat
and session.heartbeat.messageReceived are defined before capturing and replacing
the handler, and only override messageReceived when those checks pass (otherwise
skip the override) so non-WebSocket or uninitialized sessions won’t cause a
TypeError.

@cardoso cardoso force-pushed the fix/presence-dangling branch from aac4542 to e59c169 Compare December 4, 2025 16:43
@cardoso cardoso force-pushed the fix/presence-dangling branch from e59c169 to 3f106f3 Compare December 4, 2025 16:44
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/meteor/ee/server/startup/presence.ts (1)

32-47: Guard session.heartbeat and preserve messageReceived arguments

session.heartbeat is assumed to exist; if it’s undefined, accessing .messageReceived will throw. Also, the wrapper currently drops any arguments passed to messageReceived.

Consider:

-		const _messageReceived = session.heartbeat.messageReceived.bind(session.heartbeat);
-		session.heartbeat.messageReceived = function messageReceived() {
-			void Presence.updateConnection(login.user._id, login.connection.id);
-			return _messageReceived();
-		};
+		const heartbeat = session.heartbeat;
+		if (!heartbeat || typeof heartbeat.messageReceived !== 'function') {
+			return;
+		}
+
+		const originalMessageReceived = heartbeat.messageReceived.bind(heartbeat);
+		heartbeat.messageReceived = function messageReceived(...args: unknown[]) {
+			void Presence.updateConnection(login.user._id, login.connection.id);
+			return originalMessageReceived(...args);
+		};

This avoids crashes on sessions without heartbeat and keeps the original handler behavior intact.

🧹 Nitpick comments (4)
packages/model-typings/src/models/IUsersSessionsModel.ts (1)

6-10: New model methods are well-typed; consider aligning all public APIs

updateConnectionById and findStaleConnections signatures match the raw implementation and intended usage.

If removeConnectionsByConnectionIds from UsersSessionsRaw is meant to be used through the proxied model, consider adding it here as well to keep the public typings in sync.

packages/models/src/models/UsersSessions.ts (1)

48-85: DB helpers are correct; consider usage and typings

The three helpers are structurally sound:

  • updateConnectionById correctly targets a single connection via the positional operator.
  • findStaleConnections and removeConnectionsByConnectionIds express the intended queries/updates.

If findStaleConnections / removeConnectionsByConnectionIds are not actually used, consider either wiring them into the presence cleanup flow (instead of duplicating logic there) or removing them to avoid unused API surface. You might also narrow the cursor type or projection for findStaleConnections if only _id is needed.

ee/packages/presence/src/Presence.ts (2)

160-173: Reconsider hard‑coded test user skip and console logging

updateConnection/removeConnection are production presence paths, but removeConnection now special‑cases uid === 'rocketchat.internal.admin.test' and logs directly to console.log. This bakes a test‑only concern and noisy logging into core presence handling.

If this is only for local or CI testing, consider:

  • Guarding it behind an explicit test flag or environment check, or
  • Moving the behavior into test helpers/fakes instead of the main service.

This keeps production behavior and logs clean.

Also applies to: 245-249


160-173: updateConnection behavior is sound; consider using the new model helper consistently

Within this service, updateConnection correctly:

  • Throttles per session via shouldUpdateConnectionStatus.
  • Uses UsersSessions.updateConnectionById and checks modifiedCount.
  • Calls updateUserPresence only when the document actually changed.

Given you’ve also added findStaleConnections / removeConnectionsByConnectionIds on the model, consider using those helpers in removeStaleConnections for consistency and to centralize connection‑level query logic in the model layer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a65f79b and 3f106f3.

📒 Files selected for processing (9)
  • apps/meteor/ee/server/startup/presence.ts (1 hunks)
  • ee/apps/ddp-streamer/src/DDPStreamer.ts (1 hunks)
  • ee/packages/presence/src/Presence.ts (5 hunks)
  • ee/packages/presence/src/lib/constants.ts (1 hunks)
  • ee/packages/presence/src/lib/processConnectionStatus.ts (2 hunks)
  • ee/packages/presence/tests/lib/processConnectionStatus.test.ts (1 hunks)
  • packages/core-services/src/types/IPresence.ts (1 hunks)
  • packages/model-typings/src/models/IUsersSessionsModel.ts (1 hunks)
  • packages/models/src/models/UsersSessions.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • ee/packages/presence/src/lib/processConnectionStatus.ts
  • ee/apps/ddp-streamer/src/DDPStreamer.ts
  • ee/packages/presence/tests/lib/processConnectionStatus.test.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • ee/packages/presence/src/lib/constants.ts
  • packages/models/src/models/UsersSessions.ts
  • apps/meteor/ee/server/startup/presence.ts
  • packages/model-typings/src/models/IUsersSessionsModel.ts
  • ee/packages/presence/src/Presence.ts
  • packages/core-services/src/types/IPresence.ts
🧠 Learnings (1)
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: UserBridge.doGetUserRoomIds in packages/apps-engine/src/server/bridges/UserBridge.ts has a bug where it implicitly returns undefined when the app lacks read permission (missing return statement in the else case of the permission check).

Applied to files:

  • apps/meteor/ee/server/startup/presence.ts
🧬 Code graph analysis (3)
apps/meteor/ee/server/startup/presence.ts (1)
ee/packages/presence/src/Presence.ts (1)
  • Presence (11-393)
packages/model-typings/src/models/IUsersSessionsModel.ts (1)
packages/core-typings/src/IUserSession.ts (1)
  • IUserSession (11-14)
ee/packages/presence/src/Presence.ts (3)
ee/packages/presence/src/lib/constants.ts (2)
  • UPDATE_INTERVAL (9-9)
  • STALE_THRESHOLD (14-14)
packages/models/src/index.ts (1)
  • UsersSessions (216-216)
packages/core-typings/src/IUserSession.ts (1)
  • IUserSession (11-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (2)
ee/packages/presence/src/lib/constants.ts (1)

1-14: Constants definitions look good

Names and values are clear and consistent with their intended usage in presence logic.

packages/core-services/src/types/IPresence.ts (1)

5-20: Current setConnectionStatus signature is correct and consistent

The interface and implementation both use setConnectionStatus(uid: string, status: UserStatus, session: string), and all call sites adhere to this order. The distinction between bumping _updatedAt and updating status is already handled by separate methods: updateConnection(uid, session) updates the timestamp only, while setConnectionStatus(uid, status, session) updates the connection status. Status is a required parameter by design—there is no indication that it should be made optional.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
ee/packages/presence/src/Presence.ts (3)

28-34: staleConInterval is declared but never assigned.

The staleConInterval field is cleared in stopped() (line 115) but is never set anywhere in the code. This appears to be leftover from refactoring. Consider removing it if unused, or wire it up if intended.

-	private staleConInterval?: NodeJS.Timeout;
-

Also update stopped():

 	async stopped(): Promise<void> {
 		clearTimeout(this.lostConTimeout);
-		clearInterval(this.staleConInterval);
+		// Consider adding: this.reaper.stop() once stop() is implemented
 	}

159-169: Consider removing or guarding verbose debug logging.

The console.debug on line 160 will fire on every WebSocket ping, potentially creating significant log noise in production. Consider removing it or guarding with a debug flag.

 	async updateConnection(uid: string, session: string): Promise<{ uid: string; session: string } | undefined> {
-		console.debug(`Updating connection for user ${uid} and session ${session}`);
 		const result = await UsersSessions.updateConnectionById(uid, session);

171-234: Remove commented-out code.

This large block of commented-out code appears to be superseded by the PresenceReaper implementation. Dead code adds noise and should be removed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3f106f3 and 8db5df2.

📒 Files selected for processing (4)
  • ee/packages/presence/src/Presence.ts (5 hunks)
  • ee/packages/presence/src/lib/PresenceReaper.spec.ts (1 hunks)
  • ee/packages/presence/src/lib/PresenceReaper.ts (1 hunks)
  • ee/packages/presence/src/lib/processConnectionStatus.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • ee/packages/presence/src/lib/processConnectionStatus.ts
  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
  • ee/packages/presence/src/lib/PresenceReaper.ts
  • ee/packages/presence/src/Presence.ts
**/*.spec.ts

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.spec.ts: Use descriptive test names that clearly communicate expected behavior in Playwright tests
Use .spec.ts extension for test files (e.g., login.spec.ts)

Files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
🧠 Learnings (10)
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure tests run reliably in parallel without shared state conflicts

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Utilize Playwright fixtures (`test`, `page`, `expect`) for consistency in test files

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Maintain test isolation between test cases in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure clean state for each test execution in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `test.step()` for complex test scenarios to improve organization in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to **/*.spec.ts : Use descriptive test names that clearly communicate expected behavior in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Group related tests in the same file

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `test.beforeAll()` and `test.afterAll()` for setup/teardown in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.{ts,spec.ts} : Follow Page Object Model pattern consistently in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Implement proper wait strategies for dynamic content in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
🧬 Code graph analysis (3)
ee/packages/presence/src/lib/PresenceReaper.spec.ts (3)
ee/packages/presence/src/lib/PresenceReaper.ts (1)
  • PresenceReaper (13-131)
packages/model-typings/src/models/IUsersSessionsModel.ts (1)
  • IUsersSessionsModel (6-20)
packages/core-typings/src/IUserSession.ts (1)
  • IUserSession (11-14)
ee/packages/presence/src/lib/PresenceReaper.ts (2)
packages/model-typings/src/models/IUsersSessionsModel.ts (1)
  • IUsersSessionsModel (6-20)
packages/core-typings/src/IUserSession.ts (1)
  • IUserSession (11-14)
ee/packages/presence/src/Presence.ts (2)
ee/packages/presence/src/lib/PresenceReaper.ts (1)
  • PresenceReaper (13-131)
packages/models/src/index.ts (1)
  • UsersSessions (216-216)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (13)
ee/packages/presence/src/lib/processConnectionStatus.ts (1)

1-1: LGTM!

Clean import consolidation combining value and type imports in a single statement.

ee/packages/presence/src/lib/PresenceReaper.spec.ts (4)

1-11: LGTM!

Imports and mock type definition are appropriate for testing the PresenceReaper functionality.


20-34: LGTM!

Test setup provides good isolation with beforeEach and appropriate mocking of dependencies.


36-88: LGTM!

Tests thoroughly cover the processDocument business logic including stale connection identification and offline status determination. Test names clearly communicate expected behavior.


90-133: LGTM!

Integration test validates the complete run() flow including correct bulkWrite operations and onUpdate callback invocation.

ee/packages/presence/src/lib/PresenceReaper.ts (4)

1-11: LGTM!

Type definitions are well-structured and capture the necessary state for the reaping process.


24-32: LGTM!

Constructor properly initializes the reaper with sensible defaults (5-minute stale threshold, 500 batch size).


46-73: LGTM!

The run() method correctly implements batch processing with proper cutoff calculation and memory-conscious batch flushing.


99-112: Good race condition mitigation.

The $pull operation correctly includes both id and _updatedAt conditions, ensuring connections refreshed after the initial scan are not incorrectly removed.

ee/packages/presence/src/Presence.ts (4)

7-13: LGTM!

Import and exported constant are appropriate for the presence service configuration.


89-91: Reaper is started but never stopped.

The reaper is initialized and started here, but there's no corresponding stop() call in the stopped() method. Once PresenceReaper.stop() is implemented (per earlier comment), it should be called in stopped().


103-111: LGTM!

Handler correctly processes reaper updates with appropriate logging and concurrent user presence updates.


237-240: Remove this comment — the check is intentional test infrastructure, not problematic hardcoding.

The rocketchat.internal.admin.test user is explicitly created only when process.env.TEST_MODE === 'true' (in apps/meteor/server/startup/initialData.js) and the skip is by design to prevent cleanup of this test fixture during connection handling. This is a standard test support pattern, not a maintenance risk or security concern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/meteor/ee/server/startup/presence.ts (1)

43-43: Missing null check for session.heartbeat before accessing its properties.

This issue was previously flagged but remains unaddressed. If session.heartbeat is undefined (possible for non-WebSocket transports or during session initialization), accessing .messageReceived will throw a TypeError and crash the login handler.

Apply this diff to guard the heartbeat access:

+		if (!session.heartbeat || !session.heartbeat.messageReceived) {
+			return;
+		}
+
 		const _messageReceived = session.heartbeat.messageReceived.bind(session.heartbeat);
 		session.heartbeat.messageReceived = function messageReceived() {
 			if (this._seenPacket === false) {
 				void Presence.updateConnection(login.user._id, login.connection.id);
 			}
 			return _messageReceived();
 		};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8db5df2 and e7dbb88.

📒 Files selected for processing (1)
  • apps/meteor/ee/server/startup/presence.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/ee/server/startup/presence.ts
🧠 Learnings (1)
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: UserBridge.doGetUserRoomIds in packages/apps-engine/src/server/bridges/UserBridge.ts has a bug where it implicitly returns undefined when the app lacks read permission (missing return statement in the else case of the permission check).

Applied to files:

  • apps/meteor/ee/server/startup/presence.ts
🧬 Code graph analysis (1)
apps/meteor/ee/server/startup/presence.ts (1)
ee/packages/presence/src/Presence.ts (1)
  • Presence (15-384)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 📦 Meteor Build (coverage)
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build

@cardoso cardoso force-pushed the fix/presence-dangling branch from 445db39 to ff983de Compare December 5, 2025 12:35
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ee/packages/presence/src/Presence.ts (1)

108-113: Resource leak: reaper is not stopped in service lifecycle.

The reaper interval continues running after the service stops, which can cause memory leaks and unexpected behavior. This was previously flagged but remains unaddressed.

Implement PresenceReaper.stop() to clear its interval, then call it here:

 	async stopped(): Promise<void> {
+		this.reaper?.stop();
 		if (!this.lostConTimeout) {
 			return;
 		}
 		clearTimeout(this.lostConTimeout);
 	}

Note: You'll also need to add the stop() method to PresenceReaper (in ee/packages/presence/src/lib/PresenceReaper.ts):

private intervalHandle?: NodeJS.Timeout;

public start() {
	if (this.running) return;
	this.running = true;

	this.intervalHandle = setInterval(() => {
		this.run().catch((err) => console.error('[PresenceReaper] Error:', err));
	}, 60 * 1000);

	console.log('[PresenceReaper] Service started.');
}

public stop() {
	if (this.intervalHandle) {
		clearInterval(this.intervalHandle);
		this.intervalHandle = undefined;
	}
	this.running = false;
}
🧹 Nitpick comments (3)
ee/packages/presence/src/Presence.ts (3)

98-106: Add error handling for batch presence updates.

Promise.all will reject if any single updateUserPresence call fails, potentially leaving other users in the batch with stale presence state. Consider using Promise.allSettled or wrapping each update in a try-catch to ensure partial failures don't block the entire batch.

-		await Promise.all(userIds.map((uid) => this.updateUserPresence(uid)));
+		const results = await Promise.allSettled(userIds.map((uid) => this.updateUserPresence(uid)));
+		const failures = results.filter((r) => r.status === 'rejected');
+		if (failures.length > 0) {
+			console.error(`[PresenceReaper] Failed to update ${failures.length} users.`);
+		}

156-166: LGTM: Clean connection update flow.

The method correctly updates the connection timestamp, early-returns on no-op updates, and refreshes user presence. This aligns with the PR objective of handling heartbeats to prevent dangling sessions.

Optional: Consider replacing console.debug with a structured logging framework for consistency with production logging practices.


169-172: Test-specific logic hardcoded in production code.

The hardcoded check for 'rocketchat.internal.admin.test' should be externalized to configuration or an environment variable to keep test logic separate from production code.

Consider one of these approaches:

Option 1: Environment variable

+	private readonly testUserId = process.env.PRESENCE_TEST_USER_ID;
+
 	async removeConnection(uid: string | undefined, session: string | undefined): Promise<{ uid: string; session: string } | undefined> {
-		if (uid === 'rocketchat.internal.admin.test') {
-			console.log('Admin detected, skipping removal of connection for testing purposes.');
+		if (this.testUserId && uid === this.testUserId) {
+			console.log(`Test user ${uid} detected, skipping removal of connection.`);
 			return;
 		}

Option 2: Settings-based flag

 	async removeConnection(uid: string | undefined, session: string | undefined): Promise<{ uid: string; session: string } | undefined> {
+		const testUser = await Settings.getValueById('Presence_Test_User_Id');
+		if (testUser && uid === testUser) {
 			return;
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e7dbb88 and ff983de.

📒 Files selected for processing (1)
  • ee/packages/presence/src/Presence.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • ee/packages/presence/src/Presence.ts
🧬 Code graph analysis (1)
ee/packages/presence/src/Presence.ts (2)
ee/packages/presence/src/lib/PresenceReaper.ts (1)
  • PresenceReaper (13-131)
packages/models/src/index.ts (1)
  • UsersSessions (216-216)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 📦 Meteor Build (coverage)
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 🔎 Code Check / Code Lint
🔇 Additional comments (2)
ee/packages/presence/src/Presence.ts (2)

7-7: LGTM: Clean integration of PresenceReaper.

The import and field declaration properly introduce the reaper component for managing stale connection cleanup.

Also applies to: 29-29


84-85: LGTM: Proper reaper initialization.

The reaper is correctly instantiated with the UsersSessions model and callback, then started. The callback binding to handleReaperUpdates establishes the update flow for stale connection cleanup.

Adds resilience and configurability to presence reaping and connection updates to prevent dangling/stale connections and unhandled errors.

- Ensure async connection updates on heartbeat are caught and logged to avoid unhandled rejections.
- Refactor presence reaper to accept options (usersSessions, onUpdate, stale threshold, batch size), use a typed non-empty callback, and manage its interval lifecycle (start/stop).
- Make reaper notify via batched callbacks and treat empty batches safely.
- Update Presence service to instantiate and start/stop the reaper correctly and adapt to the new callback signature.
- Simplify updateConnection to use a direct updateOne query and return connectionId for clarity.
- Expand and harden tests for PresenceReaper (empty cursor handling, batching behavior).

These changes reduce dangling connections, improve testability, and make reaper behavior configurable and safer in production.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
ee/packages/presence/src/Presence.ts (1)

157-179: Core logic is correct; consider removing debug logging.

The updateConnection method correctly:

  • Uses the positional operator $ to update only the matched connection
  • Returns undefined when no modification occurs
  • Triggers presence recalculation on successful update

Consider removing the console.debug at line 158 as per coding guidelines (avoid code comments/debug artifacts in implementation). The codebase does have other console.log statements for operational logging, so if this debug output serves a specific troubleshooting need, it could remain, but otherwise it's a candidate for removal.

ee/packages/presence/src/lib/PresenceReaper.ts (1)

1-24: LGTM!

The type definitions are well-structured. The NonEmptyArray type guard pattern at lines 13-15 is a good practice for ensuring type safety when invoking the callback.

Note: The explicit import of setInterval from 'node:timers' at line 1 is valid but unusual since setInterval is a Node.js global. This might be a style preference for explicitness or to aid type inference. If consistency with the rest of the codebase is preferred, this import could be removed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff983de and 96b25ce.

📒 Files selected for processing (5)
  • apps/meteor/ee/server/startup/presence.ts (1 hunks)
  • ee/packages/presence/src/Presence.ts (5 hunks)
  • ee/packages/presence/src/lib/PresenceReaper.spec.ts (1 hunks)
  • ee/packages/presence/src/lib/PresenceReaper.ts (1 hunks)
  • packages/core-services/src/types/IPresence.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/ee/server/startup/presence.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
  • ee/packages/presence/src/lib/PresenceReaper.ts
  • packages/core-services/src/types/IPresence.ts
  • ee/packages/presence/src/Presence.ts
**/*.spec.ts

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.spec.ts: Use descriptive test names that clearly communicate expected behavior in Playwright tests
Use .spec.ts extension for test files (e.g., login.spec.ts)

Files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
🧠 Learnings (9)
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure tests run reliably in parallel without shared state conflicts

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Utilize Playwright fixtures (`test`, `page`, `expect`) for consistency in test files

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Maintain test isolation between test cases in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `test.step()` for complex test scenarios to improve organization in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure clean state for each test execution in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to **/*.spec.ts : Use descriptive test names that clearly communicate expected behavior in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Group related tests in the same file

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Implement proper wait strategies for dynamic content in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `test.beforeAll()` and `test.afterAll()` for setup/teardown in Playwright tests

Applied to files:

  • ee/packages/presence/src/lib/PresenceReaper.spec.ts
🧬 Code graph analysis (2)
ee/packages/presence/src/lib/PresenceReaper.ts (2)
packages/model-typings/src/models/IUsersSessionsModel.ts (1)
  • IUsersSessionsModel (6-18)
packages/core-typings/src/IUserSession.ts (1)
  • IUserSession (11-14)
ee/packages/presence/src/Presence.ts (2)
ee/packages/presence/src/lib/PresenceReaper.ts (1)
  • PresenceReaper (26-156)
packages/models/src/index.ts (1)
  • UsersSessions (216-216)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (12)
packages/core-services/src/types/IPresence.ts (1)

16-16: LGTM!

The new updateConnection method signature is clear and type-safe, properly expressing the success case (returning the uid/connectionId pair) and failure case (undefined).

ee/packages/presence/src/lib/PresenceReaper.spec.ts (3)

20-39: LGTM!

The test setup is well-structured with appropriate mocks. Using a small batchSize: 2 effectively tests the batching logic without requiring many test documents.


41-93: Well-structured business logic tests.

The tests thoroughly validate the core stale connection identification logic. Using @ts-expect-error to test private methods is an acceptable pattern for unit testing internal behavior.


95-170: LGTM!

The integration tests provide comprehensive coverage of the reaper's behavior, including edge cases (empty collections), core operations (bulkWrite), and batching logic (multiple users with batch boundaries).

ee/packages/presence/src/Presence.ts (4)

29-39: LGTM!

The PresenceReaper is properly configured with appropriate values (5-minute stale threshold, 500-item batch size) and correctly wired to the handleReaperUpdates callback.


86-86: LGTM!

The reaper is correctly started during service initialization.


103-106: LGTM!

The handleReaperUpdates callback correctly processes batches of user IDs reported by the reaper, triggering presence updates for each affected user.


108-114: LGTM!

The reaper is properly stopped during service shutdown, addressing the cleanup concern from previous reviews.

ee/packages/presence/src/lib/PresenceReaper.ts (4)

47-69: LGTM!

The start/stop lifecycle is correctly implemented with:

  • Guards against duplicate intervals
  • Proper error handling to prevent uncaught promise rejections
  • Clean interval cleanup

This addresses the previous review concern about missing stop mechanism.


71-98: LGTM!

The main run() logic is well-structured:

  • Calculates cutoff once for consistency
  • Uses projection to minimize data transfer
  • Implements efficient batching with final flush for remainder

100-116: LGTM!

The processDocument logic correctly:

  • Separates stale from valid connections based on the cutoff
  • Determines offline status only when all connections are stale
  • Preserves the cutoffDate for race-condition protection in the bulkWrite operation

118-155: LGTM! Race condition properly mitigated.

The flushBatch implementation correctly addresses the race condition concern from previous reviews. At lines 131-132, the $pull operation includes both the connection id and the _updatedAt condition, ensuring that connections refreshed after the initial query are not removed.

The direct access to col.bulkWrite at line 148 bypasses the model abstraction, but this appears intentional since IUsersSessionsModel doesn't expose a bulkWrite method, and this pattern is consistent with MongoDB driver usage for batch operations.

Comment on lines 182 to 185
if (uid === 'rocketchat.internal.admin.test') {
console.log('Admin detected, skipping removal of connection for testing purposes.');
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test-specific logic in production code path.

The hardcoded check for 'rocketchat.internal.admin.test' is a test-specific workaround in the production code path. This violates separation of concerns and could cause confusion or unintended behavior if this user ID appears in production.

Consider one of these alternatives:

  • Use dependency injection to provide a test-specific implementation of the removal logic
  • Configure excluded user IDs via environment variables or configuration
  • Mock the removeConnection method entirely in tests rather than relying on special-case logic in the implementation
 async removeConnection(uid: string | undefined, session: string | undefined): Promise<{ uid: string; session: string } | undefined> {
-		if (uid === 'rocketchat.internal.admin.test') {
-			console.log('Admin detected, skipping removal of connection for testing purposes.');
-			return;
-		}
 		if (!uid || !session) {
 			return;
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (uid === 'rocketchat.internal.admin.test') {
console.log('Admin detected, skipping removal of connection for testing purposes.');
return;
}
async removeConnection(uid: string | undefined, session: string | undefined): Promise<{ uid: string; session: string } | undefined> {
if (!uid || !session) {
return;
}
🤖 Prompt for AI Agents
In ee/packages/presence/src/Presence.ts around lines 182 to 185, remove the
hardcoded test-specific check for 'rocketchat.internal.admin.test' and replace
it with a testable, configurable approach: accept an injected predicate or
configuration (e.g., an excludedUserIds set or a shouldSkipRemoval(userId)
function) on the Presence instance, defaulting to none in production; update
tests to inject a predicate or mock the removeConnection method instead of
relying on the special-case; ensure the new configuration is optional and
documented so production behavior remains unchanged when not provided.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
ee/apps/ddp-streamer/src/Client.ts (1)

77-77: Idle/heartbeat state machine is sound; consider clarifying _seenPacket semantics

The _seenPacket flip in handleIdle() and subsequent check in messageReceived() gives you a clean two‑phase idle flow:

  1. After TIMEOUT of inactivity, handleIdle() pings and sets _seenPacket = false only for authenticated users (Lines 205‑208).
  2. The next inbound message flips _seenPacket back to true and triggers a presence refresh once (Lines 185‑193).

Functionally this looks correct and low‑risk.

As an optional polish, you might rename _seenPacket to something that reflects its “after idle ping” role (e.g., _seenPacketSinceIdlePing) to make the small state machine more self‑documenting and reduce cognitive load for future readers.

Also applies to: 205-211

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 96b25ce and 3b0c9d0.

📒 Files selected for processing (1)
  • ee/apps/ddp-streamer/src/Client.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • ee/apps/ddp-streamer/src/Client.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (1)
ee/apps/ddp-streamer/src/Client.ts (1)

4-4: Presence update on post-idle traffic is safe and correct

The integration pattern is sound:

  • messageReceived() is called for every parsed message, but only updates Presence when transitioning from idle (when _seenPacket was false and a userId exists)
  • The guard if (this._seenPacket || !this.userId) ensures the update runs only once per idle→active transition
  • Presence.updateConnection is idempotent—it performs an atomic MongoDB updateOne on the connection's _updatedAt timestamp, which safely handles multiple calls with the same (userId, connectionId)
  • The method gracefully handles reaped/closed connections by returning undefined when the query matches zero documents (modifiedCount === 0)
  • The fire-and-forget pattern with explicit .catch() is appropriate; failures don't block DDP message processing

This achieves the goal of bumping connection freshness when the client proves it's still alive after an idle heartbeat.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
ee/packages/presence/src/Presence.ts (1)

157-179: LGTM! Atomic and safe connection update.

The method correctly uses the positional operator to atomically update a specific connection's timestamp, validates the result, and triggers presence recalculation. The implementation is consistent with the PR objective of preventing dangling sessions by refreshing connection activity on heartbeat.

Optional: Consider reducing console.debug verbosity.

Line 158 will log on every heartbeat/ping. While console.debug is typically disabled in production environments, you might consider removing this or wrapping it in a debug flag if verbose logging becomes an issue during development.

-		console.debug(`Updating connection for user ${uid} and connection ${connectionId}`);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3b0c9d0 and 5633225.

📒 Files selected for processing (1)
  • ee/packages/presence/src/Presence.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • ee/packages/presence/src/Presence.ts
🧠 Learnings (1)
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: UserBridge.doGetUserRoomIds in packages/apps-engine/src/server/bridges/UserBridge.ts has a bug where it implicitly returns undefined when the app lacks read permission (missing return statement in the else case of the permission check).

Applied to files:

  • ee/packages/presence/src/Presence.ts
🧬 Code graph analysis (1)
ee/packages/presence/src/Presence.ts (2)
ee/packages/presence/src/lib/PresenceReaper.ts (1)
  • PresenceReaper (26-156)
packages/models/src/index.ts (1)
  • UsersSessions (216-216)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (2)
ee/packages/presence/src/Presence.ts (2)

29-39: LGTM! Clean PresenceReaper integration.

The reaper is properly configured with reasonable defaults (5-minute stale threshold, batch size of 500) and correctly wired to the handleReaperUpdates callback.


86-86: LGTM! Proper lifecycle management.

The reaper is correctly started and stopped with the service lifecycle, and the handleReaperUpdates callback efficiently processes batch updates using Promise.all.

Also applies to: 103-106, 109-109

@cardoso cardoso changed the title fix(presence): handle dangling sessions by updating status on ping fix(presence): update connections on heartbeat and remove them when stale Dec 6, 2025
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.

5 participants