Skip to content

Conversation

@nazabucciarelli
Copy link
Contributor

@nazabucciarelli nazabucciarelli commented Jan 22, 2026

Proposed changes (including videos or screenshots)

After some investigation, context gathering, and a discussion with Diego Sampaio, we aligned on the scope of this task.

The goal is to improve how DDP connection headers are handled in sauMonitorHooks.ts.
While the broader issue involves multiple places where the full connection object is passed downstream instead of only the required data, addressing all of those cases is outside the scope of this task.

Changes:

  • To make the change safer and more explicit, I will introduce dedicated types for the event payloads instead of passing the entire connection object.
  • SAU events where renamed adding 'sau.' as prefix in order to avoid confusion with already existent events, such as 'accounts.login' or 'accounts.logout'.
  • As I'm funneling the properties we pass downstream in the hook, the lower level functions where simplified, removing additional logic and making it even more readable.
  • getHeaders() function was added to extract headers in a safer way, regardless the connection was through HTTP or DDP.
  • _getConnectionInfo was removed because it was a thin wrapper with no transformation or validation logic.
    Keeping the object construction at the call site improves traceability of fields (e.g. loginToken, instanceId, httpHeaders) and reduces unnecessary indirection.
  • Improved overall code readability.

Issue(s)

CORE-1529 - Improve DDP header handling

Steps to test or reproduce

Further comments

Please keep in mind that this PR touches sensitive code, such as sessions and login/logout.

Summary by CodeRabbit

Release Notes

  • Refactor

    • Restructured internal session and authentication event handling to use strongly-typed payloads.
    • Enhanced event infrastructure systems for improved maintainability and reliability.
  • Tests

    • Added test coverage for header utility functions.

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

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Jan 22, 2026

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

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

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 Jan 22, 2026

⚠️ No Changeset found

Latest commit: 2f1df1c

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

This PR includes no changesets

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

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

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

Walkthrough

Server event architecture refactored to replace direct socket connection object passing with explicit, typed payloads. Connection metadata like userAgent, loginToken, clientAddress are now individually passed in event payloads, reducing dependency on ISocketConnection types. New getHeader utility added for safe HTTP header extraction.

Changes

Cohort / File(s) Summary
SAU Event Infrastructure
apps/meteor/server/services/sauMonitor/events.ts
Event type definitions updated: 'accounts.login', 'accounts.logout', 'socket.connected', 'socket.disconnected' renamed to sau-prefixed variants with explicit typed payloads replacing ISocketConnection/ISocketConnectionLogged dependencies.
Device Management Events
apps/meteor/server/services/device-management/events.ts
'device-login' event payload restructured: replaced connection: ISocketConnectionLogged with discrete fields userAgent, loginToken, clientAddress.
Monitor Service Implementation
apps/meteor/server/services/sauMonitor/service.ts
Login/logout/socket event handlers updated to extract and transform connection metadata; emits restructured sau-prefixed events with computed instanceId, connectionId, loginToken, clientAddress, userAgent, host.
Device Management Service
apps/meteor/server/services/device-management/service.ts
accounts.login handler refactored to destructure userId and connection, extract user-agent and client address, then emit device-login with simplified payload (userAgent, clientAddress).
SAU Monitor Hooks
apps/meteor/server/hooks/sauMonitorHooks.ts
Login/logout/socket event emission updated: computes and passes explicit metadata (loginToken, instanceId, clientAddress, userAgent, host) instead of embedding httpHeaders; removes connection object wrapping.
Monitor Logic
apps/meteor/app/statistics/server/lib/SAUMonitor.ts
\_handleSession signature refactored to accept destructured HandleSessionArgs; \_getUserAgentInfo now accepts uaString directly instead of connection object; event handlers updated to align with new payload shapes.
Device Management Session Logic
apps/meteor/ee/server/lib/deviceManagement/session.ts
device-login listener updated to destructure userAgent, loginToken, clientAddress; userAgent parsing invoked directly on provided string instead of reconstructing from connection headers.
Header Utility
packages/tools/src/getHeader.ts
New utility function introduced to safely extract header values as strings, handling both string and string[] header formats with sensible defaults.
Header Utility Tests
packages/tools/src/getHeader.spec.ts
Comprehensive test coverage added for getHeader utility covering undefined headers, missing keys, array values, and edge cases.
Utilities Export
packages/tools/src/index.ts
Public export of new getHeader function added to module barrel.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • cardoso
  • ggazzo

Poem

🐰 Hops with glee through typed payload lands,
Where connections once held all in their hands.
Now headers hop free, each field in its place,
A cleaner event surface, a well-ordered space!
The SAU monitor cheers—farewell, socket's sprawl! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: improving how DDP connection data is sent to hooks by refactoring the payload structure and event naming.
Linked Issues check ✅ Passed The PR addresses CORE-1529 by refactoring DDP header handling, introducing typed payloads instead of passing full connection objects, and renaming SAU events with proper namespacing.
Out of Scope Changes check ✅ Passed All changes are directly related to improving DDP connection data handling: new utility functions (getHeader), event payload restructuring, type definitions, and internal service/hook implementations remain in scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 chore/ddp-headers

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.

@nazabucciarelli
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

✅ Actions performed

Review triggered.

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.

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

Caution

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

⚠️ Outside diff range comments (1)
apps/meteor/app/statistics/server/lib/SAUMonitor.ts (1)

135-142: Update logout log messages to the new event name.

The warning strings still mention 'accounts.logout', which no longer matches the hook name and can mislead debugging.

🔧 Suggested fix
-			if (!userId) {
-				logger.warn({ msg: "Received 'accounts.logout' event without 'userId'" });
+			if (!userId) {
+				logger.warn({ msg: "Received 'sau.accounts.logout' event without 'userId'" });
 				return;
 			}
 
 			if (!sessionId) {
-				logger.warn({ msg: "Received 'accounts.logout' event without 'sessionId'" });
+				logger.warn({ msg: "Received 'sau.accounts.logout' event without 'sessionId'" });
 				return;
 			}
🤖 Fix all issues with AI agents
In `@packages/core-typings/src/sau/SocketConnectedPayload.ts`:
- Around line 1-3: Remove the inline comment inside the SocketConnectedPayload
type and replace the placeholder with an explicit empty payload type;
specifically, update export type SocketConnectedPayload to an explicit empty
shape (e.g., use Record<string, never> or an empty object type) so the intent is
encoded in the type rather than a comment and the inline comment is removed.
🧹 Nitpick comments (3)
apps/meteor/server/services/sauMonitor/service.ts (1)

35-38: Remove commented-out debug statement.

Per coding guidelines, avoid code comments in the implementation. This commented-out console.log should be removed.

Suggested fix
 		this.onEvent('socket.connected', async (data) => {
-			// console.log('socket.connected', data);
 			sauEvents.emit('sau.socket.connected', data);
 		});
apps/meteor/server/services/device-management/service.ts (1)

14-19: Address TODO: Add loginToken to payload.

The TODO indicates loginToken should be added to the device-login payload. The DeviceLoginPayload type already supports an optional loginToken field (as seen in session.ts usage at line 42).

Would you like me to help implement the loginToken extraction similar to how it's done in sauMonitorHooks.ts (using Accounts._hashLoginToken(resume))? Or should I open an issue to track this task?

apps/meteor/server/hooks/sauMonitorHooks.ts (1)

80-86: Remove inline comment and clarify SocketConnectedPayload intent.

The comment on line 82 should be removed per coding guidelines. Additionally, spreading the entire connection object into SocketConnectedPayload appears to contradict the PR's goal of avoiding passing full connection objects downstream. Consider extracting only the required fields.

Suggested fix

If only specific fields are needed, extract them explicitly:

 Meteor.onConnection((connection) => {
 	const socketConnectedEventObject: SocketConnectedPayload = {
-		// Implement SocketConnectedPayload type in case of using the sau.socket.connected hook
-		...connection,
+		connectionId: connection.id,
+		// Add other required fields as needed
 	};
 	sauEvents.emit('sau.socket.connected', socketConnectedEventObject);
 });

If the full connection is intentionally needed here, the comment should at minimum be removed.

@codecov
Copy link

codecov bot commented Jan 22, 2026

Codecov Report

❌ Patch coverage is 54.16667% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.77%. Comparing base (5650b78) to head (f86de12).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           develop   #38294   +/-   ##
========================================
  Coverage    70.77%   70.77%           
========================================
  Files         3159     3160    +1     
  Lines       109384   109370   -14     
  Branches     19644    19709   +65     
========================================
- Hits         77412    77407    -5     
+ Misses       29939    29929   -10     
- Partials      2033     2034    +1     
Flag Coverage Δ
e2e 60.28% <ø> (-0.07%) ⬇️
e2e-api 48.84% <54.16%> (+0.79%) ⬆️
unit 71.92% <ø> (+0.01%) ⬆️

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.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 22, 2026

📦 Docker Image Size Report

➡️ Changes

Service Current Baseline Change Percent
sum of all images 0B 0B 0B
account-service 0B 0B 0B
authorization-service 0B 0B 0B
ddp-streamer-service 0B 0B 0B
omnichannel-transcript-service 0B 0B 0B
presence-service 0B 0B 0B
queue-worker-service 0B 0B 0B
rocketchat 0B 0B 0B

📊 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/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/08 20:15", "12/09 22:17", "12/10 23:26", "12/11 21:56", "12/12 22:45", "12/13 01:34", "12/15 22:31", "12/16 22:18", "12/17 21:04", "12/18 23:12", "12/19 23:27", "12/20 21:03", "12/22 18:54", "12/23 16:16", "12/24 19:38", "12/25 17:51", "12/26 13:18", "12/29 19:01", "12/30 20:52", "01/26 19:48 (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, 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, 0.00]
  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, 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, 0.00]
  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, 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, 0.00]
  line "omnichannel-transcript-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  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, 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, 0.00]
  line "queue-worker-service" [0.14, 0.14, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.13, 0.00]
  line "rocketchat" [0.35, 0.35, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.00]
Loading

Statistics (last 30 days):

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

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

  • Tag: pr-38294
  • Baseline: develop
  • Timestamp: 2026-01-26 19:48:14 UTC
  • Historical data points: 30

Updated: Mon, 26 Jan 2026 19:48:14 GMT

@nazabucciarelli
Copy link
Contributor Author

nazabucciarelli commented Jan 22, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

✅ Actions performed

Review triggered.

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.

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)
apps/meteor/app/statistics/server/lib/SAUMonitor.ts (1)

235-245: Replace the inline TODO block with an explicit UA result type.

The comment block violates the "no implementation comments" guideline. Move the type information into code as a type alias instead.

However, the proposed solution using ReturnType<typeof UAParserMobile.uaObject> won't work as expected since UAParserCustom.js is untyped JavaScript. Define the union type explicitly instead:

♻️ Suggested approach
+	type UAResult =
+		| UAParser.IResult
+		| { device?: { type: string }; app?: { name: string; version: string }; browser?: undefined; os?: undefined }
+		| { device?: { type: string }; os?: Record<string, string>; app?: { name: string; version: string }; browser?: undefined };
+
-	const result = ((): any => {
+	const result = ((): UAResult => {

The types correspond to the three code branches: standard UAParser, mobile app, and desktop app respectively.

♻️ Duplicate comments (1)
packages/core-typings/src/sau/SocketConnectedPayload.ts (1)

1-2: Remove inline comment; encode empty payload in the type.

Line 1 violates the “no implementation comments” guideline; encode the empty payload via the type instead. As per coding guidelines, avoid code comments in implementation.

♻️ Proposed change
-export type SocketConnectedPayload = {};
+export type SocketConnectedPayload = Record<string, never>;
Is `Record<string, never>` the recommended TypeScript pattern for expressing an empty object payload?

@nazabucciarelli nazabucciarelli marked this pull request as ready for review January 23, 2026 16:47
@nazabucciarelli nazabucciarelli requested review from a team as code owners January 23, 2026 16:47
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 14 files

@nazabucciarelli
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

✅ Actions performed

Review triggered.

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.

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)
apps/meteor/app/statistics/server/lib/SAUMonitor.ts (1)

130-157: Potential logout miss when loginToken is absent.
LoginSessionPayload.loginToken and ISession.loginToken are optional, but this path treats a missing token as “session not found” and returns. That can leave sessions open and skew SAU. Consider enforcing a non-optional token in the payload or adding a fallback to close by sessionId when the token is missing.

🔧 Suggested fallback when `loginToken` is missing
-			const session = await Sessions.getLoggedInByUserIdAndSessionId<Pick<ISession, 'loginToken'>>(userId, sessionId, {
-				projection: { loginToken: 1 },
-			});
-			if (!session?.loginToken) {
+			const session = await Sessions.getLoggedInByUserIdAndSessionId<Pick<ISession, 'loginToken' | 'instanceId'>>(userId, sessionId, {
+				projection: { loginToken: 1, instanceId: 1 },
+			});
+			if (!session) {
 				if (!isProdEnv) {
 					throw new Error('Session not found during logout');
 				}
 				logger.error({ msg: 'Session not found during logout', userId, sessionId });
 				return;
 			}
+			if (!session.loginToken) {
+				await Sessions.closeByInstanceIdAndSessionId(session.instanceId, sessionId);
+				return;
+			}
 
 			await Sessions.logoutBySessionIdAndUserId({ loginToken: session.loginToken, userId });

@nazabucciarelli
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

✅ Actions performed

Review triggered.

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.

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

🤖 Fix all issues with AI agents
In `@apps/meteor/server/services/sauMonitor/events.ts`:
- Around line 4-12: The event type for 'sau.accounts.login' is inconsistent with
ISocketConnection because connection.loginToken is optional; update the event
definition in events.ts to make loginToken optional (change loginToken: string
to loginToken?: string) so it matches ISocketConnection and avoids the type
contract violation, then recompile/typecheck; alternatively, if you prefer
forcing a string, update the caller in service.ts where connection.loginToken is
passed to provide a default (e.g., connection.loginToken ?? '')—pick one
approach and apply it consistently across sau.accounts.login, service.ts, and
references in sauMonitorHooks.ts.
🧹 Nitpick comments (3)
apps/meteor/server/services/sauMonitor/service.ts (1)

42-45: Remove commented-out debug code.

The commented console.log statement should be removed to keep the codebase clean.

♻️ Proposed fix
 		this.onEvent('socket.connected', async (data) => {
-			// console.log('socket.connected', data);
 			sauEvents.emit('sau.socket.connected', { instanceId: InstanceStatus.id(), connectionId: data.id });
 		});
apps/meteor/server/hooks/sauMonitorHooks.ts (1)

49-58: Consider consolidating the two Meteor.onConnection handlers.

Both handlers register on the same connection event. Merging them would reduce overhead and improve code organization.

♻️ Proposed consolidation
 Meteor.onConnection((connection) => {
+	// in case of implementing a listener of this event, define the parameters type in services/sauMonitor/events.ts
+	sauEvents.emit('sau.socket.connected', { instanceId: InstanceStatus.id(), connectionId: connection.id });
+
 	connection.onClose(async () => {
 		sauEvents.emit('sau.socket.disconnected', { connectionId: connection.id, instanceId: InstanceStatus.id() });
 	});
 });
-
-Meteor.onConnection((connection) => {
-	// in case of implementing a listener of this event, define the parameters type in services/sauMonitor/events.ts
-	sauEvents.emit('sau.socket.connected', { instanceId: InstanceStatus.id(), connectionId: connection.id });
-});
apps/meteor/app/statistics/server/lib/SAUMonitor.ts (1)

238-307: Return type includes undefined but function always returns a value.

The function always returns { device: info } (lines 304-306), but the return type is { device: ISessionDevice } | undefined. This could lead to unnecessary null checks by callers.

♻️ Proposed fix
-	private _getUserAgentInfo(uaString: string): { device: ISessionDevice } | undefined {
+	private _getUserAgentInfo(uaString: string): { device: ISessionDevice } {

@nazabucciarelli nazabucciarelli marked this pull request as draft January 23, 2026 21:45
@nazabucciarelli nazabucciarelli marked this pull request as ready for review January 26, 2026 17:44
@nazabucciarelli
Copy link
Contributor Author

nazabucciarelli commented Jan 26, 2026

@coderabbitai review

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 8 files

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

✅ Actions performed

Review triggered.

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.

@@ -0,0 +1,11 @@
export const getHeader = (headers: unknown, key: string): string => {
Copy link
Member

Choose a reason for hiding this comment

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

This could be in the tools package.

It would also be nice to have unit tests for it.

Copy link
Member

Choose a reason for hiding this comment

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

const { resume } = methodArguments.find((arg) => 'resume' in arg) ?? {};
const loginToken = resume ? Accounts._hashLoginToken(resume) : '';
const instanceId = InstanceStatus.id();
const clientAddress = getClientAddress(info.connection);
Copy link
Member

Choose a reason for hiding this comment

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

getClientAddress internally calls for headers?.[x-real-ip] which would be undefined if headers is Headers.

It should probably use the same getheader helper.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

clientAddress is not part of the headers, so getHeader cannot be used in this case.
I kept using getClientAddress as it is already the established way to resolve the client IP and may include internal logic we don't want to bypass.

@nazabucciarelli nazabucciarelli requested a review from a team as a code owner January 26, 2026 22:53
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

🤖 Fix all issues with AI agents
In `@apps/meteor/server/hooks/sauMonitorHooks.ts`:
- Line 22: Replace the private Meteor call Accounts._hashLoginToken with the
public hashLoginToken from `@rocket.chat/account-utils`: import { hashLoginToken }
and change the expression that computes loginToken (currently using
Accounts._hashLoginToken(resume)) to use hashLoginToken(resume) (preserving the
ternary behavior with resume). Update any existing imports to add hashLoginToken
and remove reliance on Accounts._hashLoginToken so the code computes loginToken
via the public API.
- Line 38: Remove the duplicate device-login emission in sauMonitorHooks by
deleting the deviceManagementEvents.emit('device-login', { userId:
info.user._id, userAgent, loginToken, clientAddress }) call, and instead update
DeviceManagementService (the service that listens to accounts.login and emits
'device-login') to include the loginToken in its emitted payload so the single
emission covers the session handler in
apps/meteor/ee/server/lib/deviceManagement/session.ts; ensure the emitted object
from DeviceManagementService contains userId, userAgent, clientAddress and
loginToken to match the original payload.
🧹 Nitpick comments (1)
packages/tools/src/getHeader.ts (1)

3-3: Consider exporting HeaderLike type for consumers.

Consumers of getHeader might benefit from having access to the HeaderLike type alias for their own type annotations. This is optional since the function's parameter typing provides guidance.

♻️ Optional change
-type HeaderLike = IncomingHttpHeaders | Record<string, string | string[] | undefined>;
+export type HeaderLike = IncomingHttpHeaders | Record<string, string | string[] | undefined>;

}

const { resume } = methodArguments.find((arg) => 'resume' in arg) ?? {};
const loginToken = resume ? Accounts._hashLoginToken(resume) : '';
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

🧩 Analysis chain

🏁 Script executed:

# Search for all uses of Accounts._hashLoginToken in the codebase
rg "Accounts\._hashLoginToken" --type ts --type tsx --type js

Repository: RocketChat/Rocket.Chat

Length of output: 93


🏁 Script executed:

# Also check for any imports or documentation about this API
rg "_hashLoginToken" --type ts --type tsx --type js

Repository: RocketChat/Rocket.Chat

Length of output: 93


🏁 Script executed:

# Look at the full context of the file to understand the usage
cat -n apps/meteor/server/hooks/sauMonitorHooks.ts

Repository: RocketChat/Rocket.Chat

Length of output: 2359


🏁 Script executed:

# Search for all uses of _hashLoginToken without file type restrictions
rg "_hashLoginToken"

Repository: RocketChat/Rocket.Chat

Length of output: 1684


🏁 Script executed:

# Check if there's any documentation or comments about login tokens in the codebase
rg "hashLoginToken" -C 3

Repository: RocketChat/Rocket.Chat

Length of output: 12776


🏁 Script executed:

# Look for Meteor version or package information
cat package.json | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 1654


Use public hashLoginToken from @rocket.chat/account-utils instead of private Meteor API.

Accounts._hashLoginToken is a private/internal Meteor API (indicated by underscore prefix). A public equivalent, hashLoginToken, is available in the @rocket.chat/account-utils package and is already used elsewhere in this codebase for the same purpose. Replace line 22 to import and use the public API for consistency and stability.

🤖 Prompt for AI Agents
In `@apps/meteor/server/hooks/sauMonitorHooks.ts` at line 22, Replace the private
Meteor call Accounts._hashLoginToken with the public hashLoginToken from
`@rocket.chat/account-utils`: import { hashLoginToken } and change the expression
that computes loginToken (currently using Accounts._hashLoginToken(resume)) to
use hashLoginToken(resume) (preserving the ternary behavior with resume). Update
any existing imports to add hashLoginToken and remove reliance on
Accounts._hashLoginToken so the code computes loginToken via the public API.

host,
});

deviceManagementEvents.emit('device-login', { userId: info.user._id, userAgent, loginToken, clientAddress });
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all places where device-login is emitted
rg -n "emit\(['\"]device-login['\"]" --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 332


🏁 Script executed:

#!/bin/bash
# Look at the sauMonitorHooks.ts file to understand the context
cat -n apps/meteor/server/hooks/sauMonitorHooks.ts | head -60

Repository: RocketChat/Rocket.Chat

Length of output: 2359


🏁 Script executed:

#!/bin/bash
# Find and examine the DeviceManagementService
fd "device-management" --type d -x echo {} + | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Examine the DeviceManagementService to see how it handles device-login events
cat -n apps/meteor/server/services/device-management/service.ts | head -100

Repository: RocketChat/Rocket.Chat

Length of output: 1009


🏁 Script executed:

#!/bin/bash
# Check if DeviceManagementService listens to accounts.login
rg -n "accounts.login\|device-login" apps/meteor/server/services/device-management/service.ts -A 3 -B 3

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for where 'accounts.login' event is emitted (not the Accounts hook, but the service event)
rg -n "emit\(['\"]accounts\.login['\"]" --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check the sauMonitor service to see if it connects to device-management
fd "sauMonitor" --type d apps/meteor/server/services/

Repository: RocketChat/Rocket.Chat

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Check if sauMonitor listens to accounts.login or emits it
cat -n apps/meteor/server/services/sauMonitor/service.ts 2>/dev/null || echo "File not found"

Repository: RocketChat/Rocket.Chat

Length of output: 1726


🏁 Script executed:

#!/bin/bash
# Search for where the service-level 'accounts.login' event is emitted
rg -n "emit.*accounts\.login" --type ts -B 2 -A 2

Repository: RocketChat/Rocket.Chat

Length of output: 857


🏁 Script executed:

#!/bin/bash
# Search for where service events are emitted (broader search)
rg -n "registerMeteorEvents\|emit\(['\"]accounts" --type ts | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look for where Accounts.onLogin hook is connected to the service event system
rg -n "registerMeteorEvents\|Accounts\.onLogin" --type ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for event emission that bridges Accounts hooks to service events
fd ".*events.*" apps/meteor/server/services/device-management/ --type f

Repository: RocketChat/Rocket.Chat

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Check the device-management events file
cat -n apps/meteor/server/services/device-management/events.ts

Repository: RocketChat/Rocket.Chat

Length of output: 307


🏁 Script executed:

#!/bin/bash
# Search for where the service-level 'accounts.login' event is published/emitted
rg -n "publish\(.*accounts\.login\|registerMeteorEvents" --type ts -A 3 -B 3 | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look at the app/authentication directory to see how login events are published
fd ".*" apps/meteor/app/authentication/server/ --type f | grep -E "\.(ts|js)$" | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 404


🏁 Script executed:

#!/bin/bash
# Search for event publishing related to accounts
rg -n "services\.events\.emit\|publish\(" apps/meteor/server/ --type ts | grep -i login | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check the authentication hooks to understand how login events are triggered
cat -n apps/meteor/app/authentication/server/hooks/login.ts

Repository: RocketChat/Rocket.Chat

Length of output: 1249


🏁 Script executed:

#!/bin/bash
# Look for where services.events is used to emit accounts.login
rg -n "services\.events\|EventEmitter\|emitAsync" apps/meteor/server/ --type ts | grep -i "account\|login" | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for where Meteor Accounts.onLogin is bridged to the service event system
rg -n "Accounts\.onLogin\|services.*emit" apps/meteor/server/ --type ts | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for where 'accounts.login' service event is published from the Meteor hook
rg -n "publishEvent\|emitWithScope\|emit.*accounts" apps/meteor/server/ --type ts | head -40

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look at the startup directory to see how Meteor hooks are connected to services
fd ".*startup.*" apps/meteor/server/ --type f | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 104


🏁 Script executed:

#!/bin/bash
# Check if there's a bridge between Accounts.onLogin and service events
rg -n "Accounts\.on\|services\.events" apps/meteor/server/ --type ts -B 2 -A 2 | head -60

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check the startup file to understand service initialization
cat -n apps/meteor/server/services/startup.ts | head -100

Repository: RocketChat/Rocket.Chat

Length of output: 4296


🏁 Script executed:

#!/bin/bash
# Look at where onLogin hook connects to the service layer
rg -n "Accounts.onLogin" apps/meteor/server/hooks/ --type ts -B 5 -A 10

Repository: RocketChat/Rocket.Chat

Length of output: 1318


🏁 Script executed:

#!/bin/bash
# Search for where the 'accounts.login' event (service level) might be emitted
rg -n "emitWithScope\|emit.*accounts\.login" --type ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for where the service-level 'accounts.login' event is emitted 
# (the one that DeviceManagementService and SAUMonitorService listen to)
rg -n "\.emit.*accounts\.login\|services\.events\.emit" --type ts | head -40

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check the MeteorService which may bridge Meteor hooks to service events
cat -n apps/meteor/server/services/meteor/service.ts

Repository: RocketChat/Rocket.Chat

Length of output: 11802


🏁 Script executed:

#!/bin/bash
# Search for any bridge between Meteor and service events
fd "meteor" apps/meteor/server/services/ --type f | grep -E "\.ts$"

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for where the 'accounts.login' service-level event is emitted (more comprehensive)
rg -n "accounts\.login" --type ts | grep -E "emit|on" | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 1004


🏁 Script executed:

#!/bin/bash
# Search in the entire codebase for who publishes the accounts.login event
rg -n "publishEvent\|api\.events\|api\.broadcast" --type ts | grep -i login | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check if there's a connection between Meteor Accounts hooks and service events
rg -n "Accounts\.onLogin" apps/meteor/ --type ts -A 15 | head -100

Repository: RocketChat/Rocket.Chat

Length of output: 8267


🏁 Script executed:

#!/bin/bash
# Check the DDP Streamer to see how accounts.login is published
cat -n ee/apps/ddp-streamer/src/DDPStreamer.ts | sed -n '180,190p'

Repository: RocketChat/Rocket.Chat

Length of output: 467


🏁 Script executed:

#!/bin/bash
# Check if deviceManagementEvents listeners are registered and what they do
rg -n "deviceManagementEvents\.on\|deviceManagementEvents\.once" --type ts -A 5 | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check what listens to deviceManagementEvents to understand impact of duplicates
rg -n "deviceManagementEvents\.on\|deviceManagementEvents\.once" --type ts -A 3

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Confirm all paths where 'accounts.login' can be triggered
rg -n "broadcast.*accounts\.login\|emit.*accounts\.login" --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for all listeners/handlers that consume deviceManagementEvents
rg -n "on\(['\"]device-login['\"]" --type ts -A 5

Repository: RocketChat/Rocket.Chat

Length of output: 638


🏁 Script executed:

#!/bin/bash
# Verify the flow: check if both emissions happen for the same login
# Look at what triggers Accounts.onLogin vs accounts.login service event
rg -n "DDP_EVENTS.LOGGEDIN\|Accounts.onLogin" --type ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Double-check the flow by examining when both Accounts.onLogin and accounts.login service event fire
# Look at DDPStreamer more thoroughly to understand the full flow
cat -n ee/apps/ddp-streamer/src/DDPStreamer.ts | sed -n '175,195p'

Repository: RocketChat/Rocket.Chat

Length of output: 749


🏁 Script executed:

#!/bin/bash
# Check if there are any other places where accounts.login service event is broadcast
rg -n "broadcast\(['\"]accounts\.login" --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 175


Remove one device-login emission to prevent duplicates.

Line 38 emits device-login, but DeviceManagementService (line 16 of apps/meteor/server/services/device-management/service.ts) also emits it when listening to the service-level accounts.login event. Both emissions target the same listener in apps/meteor/ee/server/lib/deviceManagement/session.ts, causing the handler to run twice per login.

Additionally, the payloads differ: sauMonitorHooks includes loginToken, while DeviceManagementService omits it (see the TODO comment on line 15 of service.ts). Consolidate the logic into one place, preferably DeviceManagementService, and ensure the loginToken is included.

🤖 Prompt for AI Agents
In `@apps/meteor/server/hooks/sauMonitorHooks.ts` at line 38, Remove the duplicate
device-login emission in sauMonitorHooks by deleting the
deviceManagementEvents.emit('device-login', { userId: info.user._id, userAgent,
loginToken, clientAddress }) call, and instead update DeviceManagementService
(the service that listens to accounts.login and emits 'device-login') to include
the loginToken in its emitted payload so the single emission covers the session
handler in apps/meteor/ee/server/lib/deviceManagement/session.ts; ensure the
emitted object from DeviceManagementService contains userId, userAgent,
clientAddress and loginToken to match the original payload.

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.

3 participants