Skip to content

Conversation

@pierre-lehnen-rc
Copy link
Contributor

@pierre-lehnen-rc pierre-lehnen-rc commented Oct 27, 2025

Proposed changes (including videos or screenshots)

When an internal call ends, we now create a record on a per-user call history and detect the room id of the DM between the two users (creating it if it doesn't exist), so that we may send a message with the call's status on that room.

Issue(s)

VGA-49
VGA-50
VGA-53

Steps to test or reproduce

Further comments

The Call History itself will be expanded with API endpoints and a UI, as well as covering other types of calls too - but that work will be done with a different milestone; this PR simply adds the minimum history features needed to send the call messages on DMs.

Summary by CodeRabbit

  • New Features

    • Persist call history for internal media calls on completion (duration, state, direction, timestamps, IDs, optional room link); DM rooms resolved/created when needed.
    • New public API/event to trigger call-history updates when calls end.
  • Chores

    • Added typings and public model exports to support storing and querying call history.

@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Oct 27, 2025

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

  • 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 Oct 27, 2025

⚠️ No Changeset found

Latest commit: 7b1358e

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 Oct 27, 2025

Walkthrough

Adds call-history types, a CallHistory model with indexes and registration, media-call server API/event for history signaling, a call hangup hook to trigger updates, and service logic to compute and persist internal call-history records (duration, state, room, direction).

Changes

Cohort / File(s) Change Summary
Type Definitions
packages/core-typings/src/ICallHistoryItem.ts, packages/core-typings/src/index.ts
New call-history types and barrel export: CallHistoryItemState, ICallHistoryItem, IMediaCallHistoryItem, IInternalMediaCallHistoryItem; exported from index.
Model Typings
packages/model-typings/src/models/ICallHistoryModel.ts, packages/model-typings/src/index.ts
New exported type ICallHistoryModel = IBaseModel<CallHistoryItem> and re-exported from model-typings barrel.
Data Model & Exports
packages/models/src/models/CallHistory.ts, packages/models/src/index.ts, packages/models/src/modelClasses.ts
New CallHistoryRaw class (extends BaseRaw<CallHistoryItem>) with indexes { uid: 1, callId: 1 } (unique) and { uid: 1, ts: -1 }; proxified/exported as CallHistory.
Model Registration
apps/meteor/server/models.ts
Registered new CallHistoryRaw(db) under public key ICallHistoryModel and imported CallHistoryRaw from @rocket.chat/models.
Media Call API / Events
ee/packages/media-calls/src/definition/IMediaCallServer.ts, ee/packages/media-calls/src/server/MediaCallServer.ts
Added historyUpdate event and public method updateCallHistory(params: { callId: string }): void that emits the event.
Call Lifecycle Hook
ee/packages/media-calls/src/server/CallDirector.ts
hangupCallById now calls getMediaCallServer().updateCallHistory({ callId }) when a call is ended.
History Persistence Logic
apps/meteor/server/services/media-call/service.ts
Subscribed to historyUpdate; added logic to persist internal user-to-user call history: validate users, resolve/create DM room, compute state/duration/endedAt, and insert per-user CallHistory records for inbound/outbound directions.

Sequence Diagram(s)

sequenceDiagram
    participant CallDirector
    participant MediaCallServer
    participant MediaCallService
    participant CallHistory as CallHistory Model
    participant RoomsSvc as Rooms/DM

    CallDirector->>MediaCallServer: hangupCallById(callId)
    alt call ended
        MediaCallServer->>MediaCallServer: updateCallHistory({callId})
        MediaCallServer->>MediaCallService: emit historyUpdate(callId)
        activate MediaCallService
        MediaCallService->>MediaCallService: load call (MediaCalls)
        MediaCallService->>MediaCallService: validate internal users (2 UIDs)
        MediaCallService->>RoomsSvc: find or create DM for the two users
        RoomsSvc-->>MediaCallService: return rid
        MediaCallService->>MediaCallService: compute state, duration, endedAt
        MediaCallService->>CallHistory: insert outbound record (caller)
        MediaCallService->>CallHistory: insert inbound record (callee)
        CallHistory-->>MediaCallService: ack
        deactivate MediaCallService
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • state mapping and hangupReason handling in apps/meteor/server/services/media-call/service.ts
    • DM lookup/creation race conditions in room resolution (createDirectMessage usage)
    • uniqueness/index choices in packages/models/src/models/CallHistory.ts

Possibly related PRs

Suggested reviewers

  • tassoevan

Poem

🐰
A hop, a ring, a tiny note,
Two records saved where messages float,
Duration counted, reasons told,
Rooms found or formed when hearts unfold,
The rabbit logs each call with a grateful throat.

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'chore: initiate call history with internal calls' accurately describes the primary change: establishing the initial call history feature for internal VoIP calls.
Linked Issues check ✅ Passed All coding requirements from linked issues are met: VGA-49 call history collection created; VGA-50 call history entry persisted with required fields (date, time, duration, direction, contact, end reason); VGA-53 DM room identification and creation implemented.
Out of Scope Changes check ✅ Passed All changes directly support the specified objectives; no extraneous modifications detected outside the call history feature scope.
✨ 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 feat/voip-call-history

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.

@codecov
Copy link

codecov bot commented Oct 27, 2025

Codecov Report

❌ Patch coverage is 15.87302% with 53 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.95%. Comparing base (1acf6a3) to head (7b1358e).
⚠️ Report is 1 commits behind head on feat/voip-room-messages.

Additional details and impacted files

Impacted file tree graph

@@                     Coverage Diff                     @@
##           feat/voip-room-messages   #37316      +/-   ##
===========================================================
+ Coverage                    67.92%   67.95%   +0.02%     
===========================================================
  Files                         3356     3356              
  Lines                       114886   114943      +57     
  Branches                     20755    20781      +26     
===========================================================
+ Hits                         78042    78108      +66     
+ Misses                       34159    34137      -22     
- Partials                      2685     2698      +13     
Flag Coverage Δ
e2e 57.44% <ø> (-0.02%) ⬇️
e2e-api 40.75% <15.87%> (+0.25%) ⬆️
unit 72.03% <ø> (+0.03%) ⬆️

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

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

Copy link
Contributor Author

@pierre-lehnen-rc pierre-lehnen-rc left a comment

Choose a reason for hiding this comment

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

reminders for myself.

@pierre-lehnen-rc pierre-lehnen-rc changed the base branch from develop to feat/voip-room-messages October 29, 2025 16:39
@pierre-lehnen-rc pierre-lehnen-rc changed the title feat: voip call history (for internal calls) chore: initiate call history with internal calls Oct 29, 2025
@pierre-lehnen-rc pierre-lehnen-rc marked this pull request as ready for review October 29, 2025 16:42
@pierre-lehnen-rc pierre-lehnen-rc requested review from a team as code owners October 29, 2025 16:42
@pierre-lehnen-rc
Copy link
Contributor Author

Switched base to a feature branch so that this initial PR may be reviewed without the other pieces in place.

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 (7)
packages/models/src/models/CallHistory.ts (1)

12-17: Indexes are sensible; consider one more for future queries.

Current unique ({ uid, callId }) and sort ({ uid, ts: -1}) look right. If you anticipate call-centric lookups (e.g., troubleshooting or dedup scans), add a lightweight index on { callId: 1 } later. Optional now.

apps/meteor/server/services/media-call/service.ts (5)

21-21: Guard background task with try/catch to avoid silent failures.

Wrap saveCallToHistory in try/catch inside setImmediate to ensure any unexpected errors are logged.

- callServer.emitter.on('historyUpdate', ({ callId }) => setImmediate(() => this.saveCallToHistory(callId)));
+ callServer.emitter.on('historyUpdate', ({ callId }) =>
+ 	setImmediate(async () => {
+ 		try {
+ 			await this.saveCallToHistory(callId);
+ 		} catch (error) {
+ 			logger.error({ msg: 'failed to save call to history', callId, error });
+ 		}
+ 	}),
+ );

81-87: Scope guard looks fine; consider explicit kind/service check.

The uids.length !== 2 short-circuit is correct for internal 1:1. Optionally assert call.kind === 'direct' and call.service === 'webrtc' to future‑proof against other call types.


110-124: Insert errors are swallowed; log rejections from allSettled.

Duplicate key conflicts are expected occasionally, but other DB errors shouldn’t be silent. Capture rejections for observability.

- await Promise.allSettled([
+ const results = await Promise.allSettled([
   CallHistory.insertOne({ ...sharedData, uid: call.caller.id, direction: 'outbound', contactId: call.callee.id }),
   CallHistory.insertOne({ ...sharedData, uid: call.callee.id, direction: 'inbound', contactId: call.caller.id }),
 ]);
+ results.forEach((r, i) => {
+ 	if (r.status === 'rejected') {
+ 		logger.warn({ msg: 'failed to insert call history', callId: call._id, leg: i === 0 ? 'caller' : 'callee', error: r.reason });
+ 	}
+ });

128-136: Clamp negative durations defensively.

Clock skew or unexpected timestamps could yield negative diffs. Clamp to >= 0.

- const diff = endedAt.valueOf() - activatedAt.valueOf();
- return Math.floor(diff / 1000);
+ const diffMs = Math.max(0, endedAt.valueOf() - activatedAt.valueOf());
+ return Math.floor(diffMs / 1000);

138-160: Make hangupReason check case‑insensitive and more robust.

hangupReason shapes vary; consider a lowercase check and include common failure tokens.

- const hasError = call.hangupReason?.includes('error');
+ const reason = call.hangupReason?.toLowerCase() ?? '';
+ const hasError = /error|failed|timeout|ice|disconnect/.test(reason);
packages/core-typings/src/ICallHistoryItem.ts (1)

17-34: Core item shapes look good; consider exporting base interfaces later.

ICallHistoryItem and IMediaCallHistoryItem are internal here. If you foresee external consumers (apps or EE), exporting them could help. Optional.

📜 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 1acf6a3 and 52d33ac.

📒 Files selected for processing (12)
  • apps/meteor/server/models.ts (2 hunks)
  • apps/meteor/server/services/media-call/service.ts (3 hunks)
  • ee/packages/media-calls/src/definition/IMediaCallServer.ts (2 hunks)
  • ee/packages/media-calls/src/server/CallDirector.ts (2 hunks)
  • ee/packages/media-calls/src/server/MediaCallServer.ts (1 hunks)
  • packages/core-typings/src/ICallHistoryItem.ts (1 hunks)
  • packages/core-typings/src/index.ts (1 hunks)
  • packages/model-typings/src/index.ts (1 hunks)
  • packages/model-typings/src/models/ICallHistoryModel.ts (1 hunks)
  • packages/models/src/index.ts (2 hunks)
  • packages/models/src/modelClasses.ts (1 hunks)
  • packages/models/src/models/CallHistory.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
packages/models/src/models/CallHistory.ts (2)
packages/core-typings/src/ICallHistoryItem.ts (1)
  • CallHistoryItem (46-46)
packages/model-typings/src/models/ICallHistoryModel.ts (1)
  • ICallHistoryModel (5-5)
apps/meteor/server/models.ts (1)
packages/models/src/models/CallHistory.ts (1)
  • CallHistoryRaw (7-18)
packages/model-typings/src/models/ICallHistoryModel.ts (1)
packages/core-typings/src/ICallHistoryItem.ts (1)
  • CallHistoryItem (46-46)
packages/models/src/index.ts (2)
packages/core-services/src/index.ts (1)
  • proxify (134-134)
packages/model-typings/src/models/ICallHistoryModel.ts (1)
  • ICallHistoryModel (5-5)
ee/packages/media-calls/src/server/CallDirector.ts (1)
ee/packages/media-calls/src/server/injection.ts (1)
  • getMediaCallServer (25-32)
ee/packages/media-calls/src/server/MediaCallServer.ts (1)
ee/packages/media-calls/src/logger.ts (1)
  • logger (3-3)
apps/meteor/server/services/media-call/service.ts (4)
packages/core-typings/src/mediaCalls/IMediaCall.ts (1)
  • IMediaCall (35-68)
packages/models/src/index.ts (4)
  • MediaCalls (186-186)
  • CallHistory (149-149)
  • Rooms (204-204)
  • Users (213-213)
packages/core-typings/src/ICallHistoryItem.ts (2)
  • IInternalMediaCallHistoryItem (36-41)
  • CallHistoryItemState (5-15)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (21-95)
packages/core-typings/src/ICallHistoryItem.ts (2)
packages/core-typings/src/IUser.ts (1)
  • IUser (186-255)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (21-95)
⏰ 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). (1)
  • GitHub Check: 📦 Build Packages
🔇 Additional comments (15)
packages/model-typings/src/index.ts (1)

89-89: LGTM!

The export follows the established pattern and is correctly placed alphabetically.

packages/models/src/index.ts (2)

94-94: LGTM!

The import is correctly placed alphabetically and follows the established pattern.


149-149: LGTM!

The proxified CallHistory model export follows the established pattern and is correctly positioned alphabetically.

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

81-81: LGTM!

The re-export follows the established pattern for model classes.

packages/core-typings/src/index.ts (1)

150-150: LGTM!

The export is correctly placed near other media call-related exports and follows the established pattern.

ee/packages/media-calls/src/server/MediaCallServer.ts (1)

68-72: LGTM!

The implementation follows the established pattern of reportCallUpdate and correctly emits the historyUpdate event for consumption by history persistence logic.

ee/packages/media-calls/src/definition/IMediaCallServer.ts (1)

10-10: LGTM!

The interface additions for historyUpdate event and updateCallHistory method are consistent with existing patterns and properly typed.

Also applies to: 43-43

ee/packages/media-calls/src/server/CallDirector.ts (2)

6-6: LGTM!

The import addition is necessary for the new history update functionality.


371-377: LGTM!

The history update logic is correctly placed after the database operation and only triggers when the call actually ends (modifiedCount > 0). This defensive approach prevents duplicate history updates for already-ended calls.

apps/meteor/server/models.ts (2)

12-12: LGTM!

The import is correctly placed alphabetically and follows the established pattern.


99-99: LGTM!

The model registration follows the established pattern and correctly instantiates the CallHistoryRaw model with the database instance.

packages/model-typings/src/models/ICallHistoryModel.ts (1)

1-5: LGTM: public model typing is clean and aligned with models/raw usage.

packages/core-typings/src/ICallHistoryItem.ts (3)

5-15: State taxonomy is clear and sufficient for MVP.


36-41: Internal media call item fits service usage (contactId, rid?).

Matches persisted fields and optional rid from DM resolution. LGTM.


46-46: Alias to IInternalMediaCallHistoryItem keeps surface minimal.

Good choice for now; revisit when external call history is added.

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/server/services/media-call/service.ts (1)

168-179: Critical: DM creation can receive undefined usernames.

This issue was flagged in a previous review and remains unresolved. Users without usernames will pass the length check but produce undefined entries in the array, potentially breaking createDirectMessage.

Validate both the array length and that all usernames are defined:

-		const usernames = (await Users.findByIds(call.uids, { projection: { username: 1 } }).toArray()).map(({ username }) => username);
-		if (usernames.length !== 2) {
-			throw new Error('Invalid usernames for DM.');
-		}
+		const userDocs = await Users.findByIds(call.uids, { projection: { username: 1 } }).toArray();
+		if (userDocs.length !== 2 || userDocs.some((u) => !u.username)) {
+			throw new Error('Cannot create DM: missing usernames for one or both users.');
+		}
+		const usernames = userDocs.map((u) => u.username as string);
🧹 Nitpick comments (2)
apps/meteor/server/services/media-call/service.ts (2)

95-95: Add logging when room resolution fails.

Failed room lookups/creation are silently suppressed, making it difficult to diagnose DM creation issues.

Apply this diff to log the error:

-		const rid = await this.getRoomIdForInternalCall(call).catch(() => undefined);
+		const rid = await this.getRoomIdForInternalCall(call).catch((error) => {
+			logger.warn({ msg: 'Failed to resolve room for call history', callId: call._id, error });
+			return undefined;
+		});

110-123: Log failed history insertions.

Promise.allSettled allows partial failures to be ignored. If one insertion fails (e.g., database error), it won't be visible in logs.

Apply this diff to log failures:

-		await Promise.allSettled([
+		const results = await Promise.allSettled([
 			CallHistory.insertOne({
 				...sharedData,
 				uid: call.caller.id,
 				direction: 'outbound',
 				contactId: call.callee.id,
 			}),
 			CallHistory.insertOne({
 				...sharedData,
 				uid: call.callee.id,
 				direction: 'inbound',
 				contactId: call.caller.id,
 			}),
 		]);
+
+		results.forEach((result, index) => {
+			if (result.status === 'rejected') {
+				logger.error({ msg: 'Failed to insert call history', callId: call._id, direction: index === 0 ? 'outbound' : 'inbound', error: result.reason });
+			}
+		});
📜 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 52d33ac and 9fc98dc.

📒 Files selected for processing (1)
  • apps/meteor/server/services/media-call/service.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: pierre-lehnen-rc
PR: RocketChat/Rocket.Chat#36718
File: packages/media-signaling/src/lib/Call.ts:633-642
Timestamp: 2025-09-23T00:27:05.438Z
Learning: In PR #36718, pierre-lehnen-rc prefers to maintain consistency with the old architecture patterns for DTMF handling rather than implementing immediate validation improvements, deferring enhancements to future work.
📚 Learning: 2025-09-16T13:33:49.237Z
Learnt from: cardoso
PR: RocketChat/Rocket.Chat#36890
File: apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts:21-26
Timestamp: 2025-09-16T13:33:49.237Z
Learning: The im.delete API endpoint accepts either a `roomId` parameter (requiring the actual DM room _id) or a `username` parameter (for the DM partner's username). Constructing slug-like identifiers like `user2${Users.userE2EE.data.username}` doesn't work for this endpoint.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-16T13:33:49.237Z
Learnt from: cardoso
PR: RocketChat/Rocket.Chat#36890
File: apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts:21-26
Timestamp: 2025-09-16T13:33:49.237Z
Learning: In Rocket.Chat test files, the im.delete API endpoint accepts either a `roomId` parameter (requiring the actual DM room _id) or a `username` parameter (for the DM partner's username). It does not accept slug-like constructions such as concatenating usernames together.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#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/server/services/media-call/service.ts
📚 Learning: 2025-10-28T16:53:42.761Z
Learnt from: ricardogarim
PR: RocketChat/Rocket.Chat#37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings (mapping subscription documents to room IDs), never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings by mapping subscription documents to room IDs, never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
🧬 Code graph analysis (1)
apps/meteor/server/services/media-call/service.ts (3)
packages/core-typings/src/mediaCalls/IMediaCall.ts (1)
  • IMediaCall (35-68)
packages/models/src/index.ts (3)
  • CallHistory (149-149)
  • Rooms (204-204)
  • Users (213-213)
packages/core-typings/src/ICallHistoryItem.ts (2)
  • IInternalMediaCallHistoryItem (36-41)
  • CallHistoryItemState (5-15)
⏰ 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 (4)
apps/meteor/server/services/media-call/service.ts (4)

2-2: LGTM: Imports are appropriate for the new functionality.

All new imports are properly used in the call history implementation.

Also applies to: 6-7, 10-10


68-87: LGTM: Validation logic correctly guards history persistence.

The method properly validates call existence, completion status, and filters for internal calls before persisting.


128-136: LGTM: Duration calculation is correct.

The logic properly handles unactivated calls and converts milliseconds to seconds.


138-160: LGTM: State derivation logic correctly prioritizes conditions.

The method appropriately distinguishes between transferred, failed, error, not-answered, and ended states based on call lifecycle flags.

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/server/services/media-call/service.ts (1)

21-21: Catch saveCallToHistory failures

saveCallToHistory is async and any rejection will bubble out of setImmediate as an unhandled promise rejection, risking process termination in modern Node and hiding call-history persistence failures. Please catch and log the error (similar to the comment already raised on this line) so we know when a history write fails.

-		callServer.emitter.on('historyUpdate', ({ callId }) => setImmediate(() => this.saveCallToHistory(callId)));
+		callServer.emitter.on('historyUpdate', ({ callId }) =>
+			setImmediate(() =>
+				this.saveCallToHistory(callId).catch((error) => {
+					logger.error({ msg: 'Failed to save call to history', callId, error });
+				}),
+			),
+		);
📜 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 9fc98dc and 8c18b98.

📒 Files selected for processing (1)
  • apps/meteor/server/services/media-call/service.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: pierre-lehnen-rc
PR: RocketChat/Rocket.Chat#36718
File: packages/media-signaling/src/lib/Call.ts:633-642
Timestamp: 2025-09-23T00:27:05.438Z
Learning: In PR #36718, pierre-lehnen-rc prefers to maintain consistency with the old architecture patterns for DTMF handling rather than implementing immediate validation improvements, deferring enhancements to future work.
📚 Learning: 2025-09-16T13:33:49.237Z
Learnt from: cardoso
PR: RocketChat/Rocket.Chat#36890
File: apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts:21-26
Timestamp: 2025-09-16T13:33:49.237Z
Learning: The im.delete API endpoint accepts either a `roomId` parameter (requiring the actual DM room _id) or a `username` parameter (for the DM partner's username). Constructing slug-like identifiers like `user2${Users.userE2EE.data.username}` doesn't work for this endpoint.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-16T13:33:49.237Z
Learnt from: cardoso
PR: RocketChat/Rocket.Chat#36890
File: apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts:21-26
Timestamp: 2025-09-16T13:33:49.237Z
Learning: In Rocket.Chat test files, the im.delete API endpoint accepts either a `roomId` parameter (requiring the actual DM room _id) or a `username` parameter (for the DM partner's username). It does not accept slug-like constructions such as concatenating usernames together.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#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/server/services/media-call/service.ts
📚 Learning: 2025-10-28T16:53:42.761Z
Learnt from: ricardogarim
PR: RocketChat/Rocket.Chat#37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings (mapping subscription documents to room IDs), never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-25T09:59:26.461Z
Learnt from: Dnouv
PR: RocketChat/Rocket.Chat#37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings by mapping subscription documents to room IDs, never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
🧬 Code graph analysis (1)
apps/meteor/server/services/media-call/service.ts (4)
packages/core-typings/src/mediaCalls/IMediaCall.ts (1)
  • IMediaCall (35-68)
packages/models/src/index.ts (4)
  • MediaCalls (186-186)
  • CallHistory (149-149)
  • Rooms (204-204)
  • Users (213-213)
packages/core-typings/src/ICallHistoryItem.ts (2)
  • IInternalMediaCallHistoryItem (36-41)
  • CallHistoryItemState (5-15)
packages/core-typings/src/IRoom.ts (1)
  • IRoom (21-95)
⏰ 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
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

♻️ Duplicate comments (1)
apps/meteor/server/services/media-call/service.ts (1)

113-126: Insertion failures are logged but not surfaced to callers.

Each insertOne call now has a .catch() handler that logs errors without rethrowing. This means:

  • The promises resolve (not reject) even on database errors
  • Promise.allSettled always sees fulfilled promises
  • saveInternalCallToHistory completes successfully even if both insertions failed
  • VGA-50's requirement to "persist Call History entry" can silently fail

Per the past review and VGA-50 objectives, database insertion failures should propagate so callers know the operation failed.

Consider this approach:

-		await Promise.allSettled([
+		const results = await Promise.allSettled([
 			CallHistory.insertOne({
 				...sharedData,
 				uid: call.caller.id,
 				direction: 'outbound',
 				contactId: call.callee.id,
-			}).catch((error: unknown) => logger.error({ msg: 'Failed to insert item into Call History', error })),
+			}),
 			CallHistory.insertOne({
 				...sharedData,
 				uid: call.callee.id,
 				direction: 'inbound',
 				contactId: call.caller.id,
-			}).catch((error: unknown) => logger.error({ msg: 'Failed to insert item into Call History', error })),
+			}),
 		]);
+
+		const failures = results.filter((r) => r.status === 'rejected');
+		if (failures.length > 0) {
+			failures.forEach((f) => logger.error({ msg: 'Failed to insert item into Call History', callId: call._id, error: f.reason }));
+			throw new Error(`Failed to insert ${failures.length} call history record(s)`);
+		}
🧹 Nitpick comments (2)
apps/meteor/server/services/media-call/service.ts (2)

176-182: Consider more explicit username validation for type safety.

The current implementation filters falsy usernames and then validates the count, which works correctly at runtime. However, for better type safety and clarity, consider validating the user documents before mapping, as suggested in the past review:

-		const usernames = (await Users.findByIds(call.uids, { projection: { username: 1 } }).toArray())
-			.map(({ username }) => username)
-			.filter((username) => username);
-
-		if (usernames.length !== 2) {
-			throw new Error('Invalid usernames for DM.');
-		}
+		const userDocs = await Users.findByIds(call.uids, { projection: { username: 1 } }).toArray();
+		if (userDocs.length !== 2 || userDocs.some((u) => !u.username)) {
+			throw new Error('Cannot create DM: missing usernames for one or both users.');
+		}
+		const usernames = userDocs.map((u) => u.username as string);

This approach:

  • Makes the validation logic more explicit
  • Improves TypeScript type narrowing (username as string is safe after validation)
  • Provides a clearer error message distinguishing between missing users and missing usernames

141-163: Consider typing hangupReason explicitly or use a set-based check for error detection.

Line 50 in packages/core-typings/src/mediaCalls/IMediaCall.ts types hangupReason as string rather than the strict CallHangupReason union. While the substring match includes('error') correctly identifies all current error reasons ('signaling-error', 'service-error', 'media-error', 'input-error'), this approach relies on naming conventions rather than type enforcement.

To improve type safety and maintainability, consider either:

  • Typing hangupReason as CallHangupReason instead of string
  • Using an explicit set-based check: ['signaling-error', 'service-error', 'media-error', 'input-error'].includes(call.hangupReason)
📜 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 8c18b98 and 7b1358e.

📒 Files selected for processing (1)
  • apps/meteor/server/services/media-call/service.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: pierre-lehnen-rc
Repo: RocketChat/Rocket.Chat PR: 36718
File: packages/media-signaling/src/lib/Call.ts:633-642
Timestamp: 2025-09-23T00:27:05.438Z
Learning: In PR #36718, pierre-lehnen-rc prefers to maintain consistency with the old architecture patterns for DTMF handling rather than implementing immediate validation improvements, deferring enhancements to future work.
📚 Learning: 2025-09-16T13:33:49.237Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 36890
File: apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts:21-26
Timestamp: 2025-09-16T13:33:49.237Z
Learning: The im.delete API endpoint accepts either a `roomId` parameter (requiring the actual DM room _id) or a `username` parameter (for the DM partner's username). Constructing slug-like identifiers like `user2${Users.userE2EE.data.username}` doesn't work for this endpoint.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 Learning: 2025-09-16T13:33:49.237Z
Learnt from: cardoso
Repo: RocketChat/Rocket.Chat PR: 36890
File: apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts:21-26
Timestamp: 2025-09-16T13:33:49.237Z
Learning: In Rocket.Chat test files, the im.delete API endpoint accepts either a `roomId` parameter (requiring the actual DM room _id) or a `username` parameter (for the DM partner's username). It does not accept slug-like constructions such as concatenating usernames together.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 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/server/services/media-call/service.ts
📚 Learning: 2025-10-28T16:53:42.761Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 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: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings (mapping subscription documents to room IDs), never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
📚 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: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings by mapping subscription documents to room IDs, never undefined, even when user has no room subscriptions.

Applied to files:

  • apps/meteor/server/services/media-call/service.ts
🧬 Code graph analysis (1)
apps/meteor/server/services/media-call/service.ts (3)
packages/core-typings/src/mediaCalls/IMediaCall.ts (1)
  • IMediaCall (35-68)
packages/models/src/index.ts (4)
  • MediaCalls (186-186)
  • CallHistory (149-149)
  • Rooms (204-204)
  • Users (213-213)
packages/core-typings/src/ICallHistoryItem.ts (2)
  • IInternalMediaCallHistoryItem (36-41)
  • CallHistoryItemState (5-15)
⏰ 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 (4)
apps/meteor/server/services/media-call/service.ts (4)

68-87: LGTM: Call validation and routing logic is clear.

The method correctly validates that the call exists, has ended, and is an internal call (2 participants). The early returns for invalid scenarios are appropriate, and the TODO comment clearly indicates that external call support is planned for future work.


95-98: Verify: Should DM resolution failures allow history persistence without room ID?

The current code logs DM resolution errors but continues with rid as undefined. While line 110 makes rid optional in the history record, VGA-53's objective is to "identify the DM room for the two call participants and create that DM room if it does not yet exist."

If creating or finding the DM fails, should we:

  1. Continue persisting call history without rid (current behavior)
  2. Fail the entire history operation and propagate the error

Please clarify if persisting call history records without a valid rid is acceptable for internal calls, or if DM resolution is a hard requirement.


131-139: LGTM: Duration calculation is correct.

The method correctly computes call duration in seconds from activatedAt to endedAt, returning 0 for calls that never activated (never connected). The fallback to new Date() is defensive and appropriate.


171-174: LGTM: DM creator selection logic is robust.

The fallback chain (requesterId || callerId || call.uids[0]) appropriately handles various scenarios, ensuring a valid user ID is selected to create the DM even when createdBy is not a user-type contact.

@tassoevan tassoevan added the stat: QA assured Means it has been tested and approved by a company insider label Nov 4, 2025
@pierre-lehnen-rc pierre-lehnen-rc merged commit a8ffe29 into feat/voip-room-messages Nov 4, 2025
50 checks passed
@pierre-lehnen-rc pierre-lehnen-rc deleted the feat/voip-call-history branch November 4, 2025 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants