Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
49051b1
wip: extract required fields from DDP connection in auth hooks
nazabucciarelli Jan 19, 2026
3ca9f16
wip: flag commit
nazabucciarelli Jan 20, 2026
0b0a9a2
run linter and fix DeviceLoginPayload type issue
nazabucciarelli Jan 21, 2026
7bbe051
add LogoutSesionPayload type use
nazabucciarelli Jan 21, 2026
d02ff34
add properties funneling on sau events
nazabucciarelli Jan 21, 2026
3d6bfed
move types to sau folder, enhance socket.disconnected parameters
nazabucciarelli Jan 22, 2026
83f3da5
Merge branch 'develop' into chore/ddp-headers
nazabucciarelli Jan 22, 2026
4aea9c5
prettier fix
nazabucciarelli Jan 22, 2026
11eaf8c
edit logger message
nazabucciarelli Jan 22, 2026
668c2a3
remove unreachable code
nazabucciarelli Jan 23, 2026
fe4ed1f
Merge branch 'develop' into chore/ddp-headers
nazabucciarelli Jan 23, 2026
ba3990c
add forgotten loginToken
nazabucciarelli Jan 23, 2026
3c5c1e0
Merge branch 'chore/ddp-headers' of github.com:RocketChat/Rocket.Chat…
nazabucciarelli Jan 23, 2026
ecf59a7
remove use of created types
nazabucciarelli Jan 23, 2026
e76d829
add optional operator to loginToken
nazabucciarelli Jan 23, 2026
4ed51c6
add optional to loginToken
nazabucciarelli Jan 26, 2026
b94212c
Merge branch 'develop' into chore/ddp-headers
nazabucciarelli Jan 26, 2026
7586e19
modify instanceId param
nazabucciarelli Jan 26, 2026
9c2b6db
Merge branch 'chore/ddp-headers' of github.com:RocketChat/Rocket.Chat…
nazabucciarelli Jan 26, 2026
5cf8c84
Merge branch 'develop' into chore/ddp-headers
nazabucciarelli Jan 26, 2026
eac64ef
rollback deno.lock changes by accident
nazabucciarelli Jan 26, 2026
b65be50
Merge branch 'chore/ddp-headers' of github.com:RocketChat/Rocket.Chat…
nazabucciarelli Jan 26, 2026
9ce09a4
make code cleaner
nazabucciarelli Jan 26, 2026
f86de12
Apply suggestion from @KevLehman
nazabucciarelli Jan 26, 2026
73403e2
enhance getHeader method and add unit tests
nazabucciarelli Jan 26, 2026
2a47751
Merge branch 'chore/ddp-headers' of github.com:RocketChat/Rocket.Chat…
nazabucciarelli Jan 26, 2026
64cf87c
rollback deno.lock (again)
nazabucciarelli Jan 27, 2026
6d3d051
change hashLoginToken method
nazabucciarelli Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 33 additions & 49 deletions apps/meteor/app/statistics/server/lib/SAUMonitor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ISession, ISessionDevice, ISocketConnectionLogged, IUser } from '@rocket.chat/core-typings';
import type { ISession, ISessionDevice, IUser } from '@rocket.chat/core-typings';
import { cronJobs } from '@rocket.chat/cron';
import { Logger } from '@rocket.chat/logger';
import { Sessions, Users, aggregates } from '@rocket.chat/models';
Expand All @@ -8,7 +8,6 @@ import UAParser from 'ua-parser-js';

import { UAParserMobile, UAParserDesktop } from './UAParserCustom';
import { getMostImportantRole } from '../../../../lib/roles/getMostImportantRole';
import { getClientAddress } from '../../../../server/lib/getClientAddress';
import { sauEvents } from '../../../../server/services/sauMonitor/events';

type DateObj = { day: number; month: number; year: number };
Expand All @@ -32,6 +31,16 @@ const getUserRoles = mem(

const isProdEnv = process.env.NODE_ENV === 'production';

type HandleSessionArgs = {
userId: string;
instanceId: string;
userAgent: string;
loginToken?: string;
connectionId: string;
clientAddress: string;
host: string;
};

/**
* Server Session Monitor for SAU(Simultaneously Active Users) based on Meteor server sessions
*/
Expand Down Expand Up @@ -97,12 +106,12 @@ export class SAUMonitorClass {
return;
}

sauEvents.on('socket.disconnected', async ({ id, instanceId }) => {
sauEvents.on('sau.socket.disconnected', async ({ connectionId, instanceId }) => {
if (!this.isRunning()) {
return;
}

await Sessions.closeByInstanceIdAndSessionId(instanceId, id);
await Sessions.closeByInstanceIdAndSessionId(instanceId, connectionId);
});
}

Expand All @@ -111,7 +120,7 @@ export class SAUMonitorClass {
return;
}

sauEvents.on('accounts.login', async ({ userId, connection }) => {
sauEvents.on('sau.accounts.login', async ({ userId, instanceId, userAgent, loginToken, connectionId, clientAddress, host }) => {
if (!this.isRunning()) {
return;
}
Expand All @@ -121,23 +130,22 @@ export class SAUMonitorClass {
const mostImportantRole = getMostImportantRole(roles);

const loginAt = new Date();
const params = { userId, roles, mostImportantRole, loginAt, ...getDateObj() };
await this._handleSession(connection, params);
const params = { roles, mostImportantRole, loginAt, ...getDateObj() };
await this._handleSession({ userId, instanceId, userAgent, loginToken, connectionId, clientAddress, host }, params);
});

sauEvents.on('accounts.logout', async ({ userId, connection }) => {
sauEvents.on('sau.accounts.logout', async ({ userId, sessionId }) => {
if (!this.isRunning()) {
return;
}

if (!userId) {
logger.warn({ msg: "Received 'accounts.logout' event without 'userId'" });
logger.warn({ msg: "Received 'sau.accounts.logout' event without 'userId'" });
return;
}

const { id: sessionId } = connection;
if (!sessionId) {
logger.warn({ msg: "Received 'accounts.logout' event without 'sessionId'" });
logger.warn({ msg: "Received 'sau.accounts.logout' event without 'sessionId'" });
return;
}

Expand All @@ -157,14 +165,20 @@ export class SAUMonitorClass {
}

private async _handleSession(
connection: ISocketConnectionLogged,
params: Pick<ISession, 'userId' | 'mostImportantRole' | 'loginAt' | 'day' | 'month' | 'year' | 'roles'>,
{ userId, instanceId, userAgent, loginToken, connectionId, clientAddress, host }: HandleSessionArgs,
params: Pick<ISession, 'mostImportantRole' | 'loginAt' | 'day' | 'month' | 'year' | 'roles'>,
): Promise<void> {
const data = this._getConnectionInfo(connection, params);

if (!data) {
return;
}
const data: Omit<ISession, '_id' | '_updatedAt' | 'createdAt' | 'searchTerm'> = {
userId,
...(loginToken && { loginToken }),
ip: clientAddress,
host,
sessionId: connectionId,
instanceId,
type: 'session',
...(loginToken && this._getUserAgentInfo(userAgent)),
...params,
};

const searchTerm = this._getSearchTerm(data);

Expand Down Expand Up @@ -221,37 +235,7 @@ export class SAUMonitorClass {
.join('');
}

private _getConnectionInfo(
connection: ISocketConnectionLogged,
params: Pick<ISession, 'userId' | 'mostImportantRole' | 'loginAt' | 'day' | 'month' | 'year' | 'roles'>,
): Omit<ISession, '_id' | '_updatedAt' | 'createdAt' | 'searchTerm'> | undefined {
if (!connection) {
return;
}

const ip = getClientAddress(connection);

const host = connection.httpHeaders?.host ?? '';

return {
type: 'session',
sessionId: connection.id,
instanceId: connection.instanceId,
...(connection.loginToken && { loginToken: connection.loginToken }),
ip,
host,
...this._getUserAgentInfo(connection),
...params,
};
}

private _getUserAgentInfo(connection: ISocketConnectionLogged): { device: ISessionDevice } | undefined {
if (!connection?.httpHeaders?.['user-agent']) {
return;
}

const uaString = connection.httpHeaders['user-agent'];

private _getUserAgentInfo(uaString: string): { device: ISessionDevice } | undefined {
// TODO define a type for "result" below
// | UAParser.IResult
// | { device: { type: string; model?: string }; browser: undefined; os: undefined; app: { name: string; version: string } }
Expand Down
14 changes: 5 additions & 9 deletions apps/meteor/ee/server/lib/deviceManagement/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ const uaParser = async (
};

export const listenSessionLogin = () => {
return deviceManagementEvents.on('device-login', async ({ userId, connection }) => {
return deviceManagementEvents.on('device-login', async ({ userId, userAgent, loginToken, clientAddress }) => {
const deviceEnabled = settings.get('Device_Management_Enable_Login_Emails');

if (!deviceEnabled) {
return;
}

if (connection.loginToken) {
if (loginToken) {
return;
}

Expand Down Expand Up @@ -67,11 +67,7 @@ export const listenSessionLogin = () => {
emails: [{ address: email }],
} = user;

const userAgentString =
connection.httpHeaders instanceof Headers
? (connection.httpHeaders.get('user-agent') ?? '')
: (connection.httpHeaders['user-agent'] ?? '');
const { browser, os, device, cpu, app } = await uaParser(userAgentString);
const { browser, os, device, cpu, app } = await uaParser(userAgent);

const mailData = {
name,
Expand All @@ -81,7 +77,7 @@ export const listenSessionLogin = () => {
deviceInfo: `${device.type || t('Device_Management_Device_Unknown')} ${device.vendor || ''} ${device.model || ''} ${
cpu.architecture || ''
}`,
ipInfo: connection.clientAddress,
ipInfo: clientAddress,
userAgent: '',
date: moment().format(String(dateFormat)),
};
Expand All @@ -105,7 +101,7 @@ export const listenSessionLogin = () => {
mailData.deviceInfo = `Desktop App ${cpu.architecture || ''}`;
break;
default:
mailData.userAgent = connection.httpHeaders['user-agent'] || '';
mailData.userAgent = userAgent || '';
break;
}

Expand Down
50 changes: 23 additions & 27 deletions apps/meteor/server/hooks/sauMonitorHooks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { IncomingHttpHeaders } from 'http';

import { hashLoginToken } from '@rocket.chat/account-utils';
import { InstanceStatus } from '@rocket.chat/instance-status';
import { getHeader } from '@rocket.chat/tools';
import { Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';

import type { ILoginAttempt } from '../../app/authentication/server/ILoginAttempt';
import { getClientAddress } from '../lib/getClientAddress';
import { deviceManagementEvents } from '../services/device-management/events';
import { sauEvents } from '../services/sauMonitor/events';

Expand All @@ -19,45 +20,40 @@ Accounts.onLogin((info: ILoginAttempt) => {
}

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

const userAgent = getHeader(httpHeaders, 'user-agent');
const host = getHeader(httpHeaders, 'host');

const eventObject = {
sauEvents.emit('sau.accounts.login', {
userId: info.user._id,
connection: {
...info.connection,
...(resume && { loginToken: Accounts._hashLoginToken(resume) }),
instanceId: InstanceStatus.id(),
httpHeaders: httpHeaders as IncomingHttpHeaders,
},
};
sauEvents.emit('accounts.login', eventObject);
deviceManagementEvents.emit('device-login', eventObject);
instanceId,
userAgent,
loginToken,
connectionId: info.connection.id,
clientAddress,
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.

});

Accounts.onLogout((info) => {
const { httpHeaders } = info.connection;

if (!info.user) {
return;
}
sauEvents.emit('accounts.logout', {
userId: info.user._id,
connection: { instanceId: InstanceStatus.id(), ...info.connection, httpHeaders: httpHeaders as IncomingHttpHeaders },
});

sauEvents.emit('sau.accounts.logout', { userId: info.user._id, sessionId: info.connection.id });
});

Meteor.onConnection((connection) => {
connection.onClose(async () => {
const { httpHeaders } = connection;
sauEvents.emit('socket.disconnected', {
instanceId: InstanceStatus.id(),
...connection,
httpHeaders: httpHeaders as IncomingHttpHeaders,
});
sauEvents.emit('sau.socket.disconnected', { connectionId: connection.id, instanceId: InstanceStatus.id() });
});
});

Meteor.onConnection((connection) => {
const { httpHeaders } = connection;

sauEvents.emit('socket.connected', { instanceId: InstanceStatus.id(), ...connection, httpHeaders: httpHeaders as IncomingHttpHeaders });
// 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 });
});
3 changes: 1 addition & 2 deletions apps/meteor/server/services/device-management/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ISocketConnectionLogged } from '@rocket.chat/core-typings';
import { Emitter } from '@rocket.chat/emitter';

export const deviceManagementEvents = new Emitter<{
'device-login': { userId: string; connection: ISocketConnectionLogged };
'device-login': { userId: string; userAgent: string; loginToken?: string; clientAddress: string };
}>();
10 changes: 8 additions & 2 deletions apps/meteor/server/services/device-management/service.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import type { IDeviceManagementService } from '@rocket.chat/core-services';
import { ServiceClassInternal } from '@rocket.chat/core-services';
import { getHeader } from '@rocket.chat/tools';

import { deviceManagementEvents } from './events';
import { getClientAddress } from '../../lib/getClientAddress';

export class DeviceManagementService extends ServiceClassInternal implements IDeviceManagementService {
protected name = 'device-management';

constructor() {
super();

this.onEvent('accounts.login', async (data) => {
this.onEvent('accounts.login', async ({ userId, connection }) => {
// TODO need to add loginToken to data
deviceManagementEvents.emit('device-login', data);
deviceManagementEvents.emit('device-login', {
userId,
userAgent: getHeader(connection.httpHeaders, 'user-agent'),
clientAddress: getClientAddress(connection),
});
});
}
}
17 changes: 12 additions & 5 deletions apps/meteor/server/services/sauMonitor/events.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import type { ISocketConnection, ISocketConnectionLogged } from '@rocket.chat/core-typings';
import { Emitter } from '@rocket.chat/emitter';

export const sauEvents = new Emitter<{
'accounts.login': { userId: string; connection: ISocketConnectionLogged };
'accounts.logout': { userId: string; connection: ISocketConnection };
'socket.connected': ISocketConnection;
'socket.disconnected': ISocketConnection;
'sau.accounts.login': {
userId: string;
instanceId: string;
connectionId: string;
loginToken?: string;
clientAddress: string;
userAgent: string;
host: string;
};
'sau.accounts.logout': { userId: string; sessionId: string };
'sau.socket.connected': { instanceId: string; connectionId: string };
'sau.socket.disconnected': { instanceId: string; connectionId: string };
}>();
24 changes: 16 additions & 8 deletions apps/meteor/server/services/sauMonitor/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,39 @@

import { ServiceClassInternal } from '@rocket.chat/core-services';
import type { ISAUMonitorService } from '@rocket.chat/core-services';
import { getHeader } from '@rocket.chat/tools';

import { sauEvents } from './events';
import { getClientAddress } from '../../lib/getClientAddress';

export class SAUMonitorService extends ServiceClassInternal implements ISAUMonitorService {
protected name = 'sau-monitor';

constructor() {
super();

this.onEvent('accounts.login', async (data) => {
sauEvents.emit('accounts.login', data);
this.onEvent('accounts.login', async ({ userId, connection }) => {
sauEvents.emit('sau.accounts.login', {
userId,
instanceId: connection.instanceId,
connectionId: connection.id,
loginToken: connection.loginToken,
clientAddress: getClientAddress(connection),
userAgent: getHeader(connection.httpHeaders, 'user-agent'),
host: getHeader(connection.httpHeaders, 'host'),
});
});

this.onEvent('accounts.logout', async (data) => {
sauEvents.emit('accounts.logout', data);
this.onEvent('accounts.logout', async ({ userId, connection }) => {
sauEvents.emit('sau.accounts.logout', { userId, sessionId: connection.id });
});

this.onEvent('socket.disconnected', async (data) => {
// console.log('socket.disconnected', data);
sauEvents.emit('socket.disconnected', data);
sauEvents.emit('sau.socket.disconnected', { instanceId: data.instanceId, connectionId: data.id });
});

this.onEvent('socket.connected', async (data) => {
// console.log('socket.connected', data);
sauEvents.emit('socket.connected', data);
sauEvents.emit('sau.socket.connected', { instanceId: data.instanceId, connectionId: data.id });
});
}
}
Loading
Loading