Skip to content

feat: add user avatar update events to a fanout queue (#39907)#39950

Open
NestroyMusoke wants to merge 4 commits intoRocketChat:developfrom
NestroyMusoke:fix/39907-avatar-fanout-queue
Open

feat: add user avatar update events to a fanout queue (#39907)#39950
NestroyMusoke wants to merge 4 commits intoRocketChat:developfrom
NestroyMusoke:fix/39907-avatar-fanout-queue

Conversation

@NestroyMusoke
Copy link
Copy Markdown

@NestroyMusoke NestroyMusoke commented Mar 29, 2026

Closes #39907

Changes

Introduces a FanoutQueue utility and uses it to process user avatar
update events across federated rooms with concurrency control and retry.

New file: FanoutQueue.ts

  • Lightweight in-memory queue with configurable concurrency (default: 5)
  • Exponential backoff retry (default: 3 attempts)
  • Fire-and-forget dispatch via void queue.enqueue(...)
  • Reusable for other fanout scenarios

Modified: FederationMatrix.ts

  • Added user.avatarUpdate event handler inside created()
  • Validates user is local (skips remote federated users)
  • Constructs mxc:// avatar URL from the avatarETag
  • Enqueues per-room profile updates through the fanout queue
    instead of sequential processing

Summary by CodeRabbit

  • New Features
    • Avatar update propagation now uses a queued fanout system to each federated room with controlled concurrency and retries.
  • Refactor
    • Improved reliability of avatar update handling with automatic retry logic and exponential backoff for failed updates across federated rooms.
  • Chores
    • Package release entry added for a minor version bump.

@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot bot commented Mar 29, 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
Copy Markdown

changeset-bot bot commented Mar 29, 2026

🦋 Changeset detected

Latest commit: b8ae598

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

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

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

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 29, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6215fb9-49cf-4761-a287-36b32f1a738c

📥 Commits

Reviewing files that changed from the base of the PR and between 6028631 and df177d8.

📒 Files selected for processing (1)
  • .changeset/avatar-fanout-queue.md
✅ Files skipped from review due to trivial changes (1)
  • .changeset/avatar-fanout-queue.md

Walkthrough

Adds an in-memory FanoutQueue with configurable concurrency, retries, and exponential backoff; FederationMatrix enqueues per-room user.avatarUpdate profile updates into this queue instead of performing them inline.

Changes

Cohort / File(s) Summary
FanoutQueue Implementation
ee/packages/federation-matrix/src/FanoutQueue.ts
New exported FanoutQueueOptions and FanoutQueue class providing enqueue(id, task) with concurrency control, per-task retry limits, exponential backoff (baseDelayMs * 2 ** retries), and promise-based task resolution. Defaults: concurrency=5, maxRetries=3, baseDelayMs=1000.
Federation Matrix Avatar Updates
ee/packages/federation-matrix/src/FederationMatrix.ts
Instantiates a FanoutQueue and registers a user.avatarUpdate handler that validates/lookups the user, skips remote federated users, builds Matrix profile payloads, queries federated room IDs, and enqueues per-room federationSDK.updateUserProfile calls.
Release Notes
.changeset/avatar-fanout-queue.md
Adds a changeset marking @rocket.chat/federation-matrix for a minor release describing the new avatar fanout queue feature.

Sequence Diagram

sequenceDiagram
    actor User
    participant FedMatrix as FederationMatrix
    participant Queue as FanoutQueue
    participant SDK as federationSDK
    participant Rooms as RoomService

    User->>FedMatrix: Avatar update event
    FedMatrix->>FedMatrix: Validate & lookup local user
    FedMatrix->>FedMatrix: Build Matrix userId & avatar_url
    FedMatrix->>Rooms: Query federated room IDs
    Rooms-->>FedMatrix: Room IDs
    loop For each room
        FedMatrix->>Queue: enqueue(roomId, updateTask)
    end

    rect rgba(100, 150, 200, 0.5)
    Note over Queue: Manage concurrency & retries
    Queue->>SDK: updateUserProfile(roomId, profile)
    alt Task succeeds
        SDK-->>Queue: Success
        Queue->>Queue: Resolve promise
    else Task fails
        SDK-->>Queue: Error
        alt Retries remaining
            Queue->>Queue: Wait backoff (baseDelayMs * 2^retries) then re-enqueue
        else Max retries exhausted
            Queue->>Queue: Log error & reject promise
        end
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 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 title clearly summarizes the main change: introducing a fanout queue for user avatar update events in the federation system.
Linked Issues check ✅ Passed The PR fully implements the objective from issue #39907: introducing fanout queue processing for user avatar updates with configurable concurrency and retry logic.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objective of adding user avatar update events to a fanout queue with no unrelated modifications.
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
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/39907-avatar-fanout-queue

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.

@coderabbitai coderabbitai bot added the type: feature Pull requests that introduces new feature label Mar 29, 2026
Copy link
Copy Markdown
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.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="ee/packages/federation-matrix/src/FanoutQueue.ts">

<violation number="1" location="ee/packages/federation-matrix/src/FanoutQueue.ts:105">
P1: Exhausted queue tasks reject promises even though call sites use fire-and-forget, causing unhandled promise rejections.</violation>
</file>

<file name="ee/packages/federation-matrix/src/FederationMatrix.ts">

<violation number="1" location="ee/packages/federation-matrix/src/FederationMatrix.ts:138">
P2: Avatar fanout sends unvalidated Matrix user IDs to federation SDK, allowing malformed IDs (e.g., when `serverName` is empty) to be retried repeatedly per room.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

msg: `Task "${item.id}" exhausted all ${this.maxRetries} retries, dropping`,
err: error,
});
item.reject(error);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

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

P1: Exhausted queue tasks reject promises even though call sites use fire-and-forget, causing unhandled promise rejections.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ee/packages/federation-matrix/src/FanoutQueue.ts, line 105:

<comment>Exhausted queue tasks reject promises even though call sites use fire-and-forget, causing unhandled promise rejections.</comment>

<file context>
@@ -0,0 +1,112 @@
+					msg: `Task "${item.id}" exhausted all ${this.maxRetries} retries, dropping`,
+					err: error,
+				});
+				item.reject(error);
+			}
+		} finally {
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

154-161: Handle potential unhandled promise rejection from fire-and-forget enqueue.

Using void this.avatarFanoutQueue.enqueue(...) discards the promise but doesn't prevent unhandled rejection if the task exhausts all retries. While the queue logs the error internally, the reject(error) call in FanoutQueue.process() will trigger an unhandled promise rejection warning/error.

Consider adding .catch() to explicitly handle the rejection:

Proposed fix
-			void this.avatarFanoutQueue.enqueue(
+			this.avatarFanoutQueue.enqueue(
 				`avatar:${username}:${externalRoomId}`,
 				async () => {
 					await federationSDK.updateUserProfile(externalRoomId, matrixUserId, profilePayload);
 					this.logger.debug({ msg: 'Sent avatar update', username, roomId: externalRoomId });
 				},
-			);
+			).catch(() => {
+				// Error already logged by FanoutQueue
+			});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ee/packages/federation-matrix/src/FederationMatrix.ts` around lines 154 -
161, The enqueue call is fire-and-forget and can still emit an unhandled
rejection if FanoutQueue.process ultimately rejects; update the usage of
avatarFanoutQueue.enqueue (where `void this.avatarFanoutQueue.enqueue(...)` is
used) to attach a rejection handler (e.g., call .catch(...) on the returned
promise) that logs the error via this.logger.error and suppresses the unhandled
rejection; keep the existing internal queue logging but ensure the outer call
(the promise returned by avatarFanoutQueue.enqueue) is explicitly handled so
FanoutQueue.process rejections don’t become unhandled.
ee/packages/federation-matrix/src/FanoutQueue.ts (1)

88-106: Clarify retry logging and consider unhandled rejection risk.

Two observations:

  1. Log message clarity: The log says attempt ${item.retries + 1}/${this.maxRetries} but this represents "retry count", not "attempt count". With maxRetries=3, there are 4 total attempts (1 initial + 3 retries). Consider changing to retry ${item.retries + 1}/${this.maxRetries} for clarity.

  2. Unhandled promise rejection: When callers use fire-and-forget (void queue.enqueue(...)), the item.reject(error) on line 105 creates an unhandled promise rejection. The queue already logs the error, so the rejection may be unnecessary for fire-and-forget use cases. Consider either:

    • Swallowing the rejection internally after logging, or
    • Documenting that callers must .catch() if they discard the promise
Potential fix for log clarity
 			if (item.retries < this.maxRetries) {
 				const delay = this.baseDelayMs * 2 ** item.retries;
 				this.logger.warn({
-					msg: `Task "${item.id}" failed (attempt ${item.retries + 1}/${this.maxRetries}), retrying in ${delay}ms`,
+					msg: `Task "${item.id}" failed (retry ${item.retries + 1}/${this.maxRetries}), retrying in ${delay}ms`,
 					err: error,
 				});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ee/packages/federation-matrix/src/FanoutQueue.ts` around lines 88 - 106,
Update the retry log to use "retry" wording so the message reflects retry count
(change logger.warn msg from `attempt ${item.retries + 1}/${this.maxRetries}` to
`retry ${item.retries + 1}/${this.maxRetries}`) and prevent unhandled promise
rejections by not unconditionally calling item.reject(error) when retries are
exhausted; instead, add an "awaited" flag on enqueued items in
FanoutQueue.enqueue (set true when the caller holds the returned promise, false
for fire-and-forget), and in the exhaustion branch of the retry logic only call
item.reject(error) if item.awaited is true — otherwise just log the error and
drop the item (or resolve/suppress internally). Ensure references:
FanoutQueue.enqueue, item.retries, this.maxRetries, logger.warn, logger.error,
and item.reject are updated accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ee/packages/federation-matrix/src/FederationMatrix.ts`:
- Around line 44-48: The avatarFanoutQueue property declaration uses
inconsistent/mixed indentation compared to the surrounding class; locate the
private readonly avatarFanoutQueue = new FanoutQueue('avatar-updates', { ... })
in FederationMatrix.ts and normalize its leading spaces to match the class
member indentation style (align with other private readonly members) so the
spacing is consistent with the rest of the class.
- Around line 138-140: The MXC URI is being built from avatarETag (in
FederationMatrix: matrixUserId/avatarUrl), but avatarETag is an HTTP ETag, not a
Matrix media_id; replace usage of avatarETag with the actual file/media id
returned by your FileStore upload (or perform a media upload to Matrix to obtain
media_id) and construct avatarUrl as `mxc://<serverName>/<media_id>` instead;
locate where avatarETag is read (setUserAvatar.ts and FederationMatrix.ts) and
either change the stored field to the fileId/media_id or call an upload helper
(e.g., uploadMediaToMatrix or similar) to obtain and persist the correct
media_id before constructing avatarUrl.

---

Nitpick comments:
In `@ee/packages/federation-matrix/src/FanoutQueue.ts`:
- Around line 88-106: Update the retry log to use "retry" wording so the message
reflects retry count (change logger.warn msg from `attempt ${item.retries +
1}/${this.maxRetries}` to `retry ${item.retries + 1}/${this.maxRetries}`) and
prevent unhandled promise rejections by not unconditionally calling
item.reject(error) when retries are exhausted; instead, add an "awaited" flag on
enqueued items in FanoutQueue.enqueue (set true when the caller holds the
returned promise, false for fire-and-forget), and in the exhaustion branch of
the retry logic only call item.reject(error) if item.awaited is true — otherwise
just log the error and drop the item (or resolve/suppress internally). Ensure
references: FanoutQueue.enqueue, item.retries, this.maxRetries, logger.warn,
logger.error, and item.reject are updated accordingly.

In `@ee/packages/federation-matrix/src/FederationMatrix.ts`:
- Around line 154-161: The enqueue call is fire-and-forget and can still emit an
unhandled rejection if FanoutQueue.process ultimately rejects; update the usage
of avatarFanoutQueue.enqueue (where `void this.avatarFanoutQueue.enqueue(...)`
is used) to attach a rejection handler (e.g., call .catch(...) on the returned
promise) that logs the error via this.logger.error and suppresses the unhandled
rejection; keep the existing internal queue logging but ensure the outer call
(the promise returned by avatarFanoutQueue.enqueue) is explicitly handled so
FanoutQueue.process rejections don’t become unhandled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4dfae03-97ae-42e3-a329-a1c97dca6223

📥 Commits

Reviewing files that changed from the base of the PR and between 4235cd9 and 6028631.

📒 Files selected for processing (2)
  • ee/packages/federation-matrix/src/FanoutQueue.ts
  • ee/packages/federation-matrix/src/FederationMatrix.ts
📜 Review details
⏰ 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: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

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

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

Files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • ee/packages/federation-matrix/src/FanoutQueue.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: smirk-dev
Repo: RocketChat/Rocket.Chat PR: 39625
File: apps/meteor/app/api/server/v1/push.ts:85-97
Timestamp: 2026-03-14T14:58:58.834Z
Learning: In RocketChat/Rocket.Chat, the `push.token` POST/DELETE endpoints in `apps/meteor/app/api/server/v1/push.ts` were already migrated to the chained router API pattern on `develop` prior to PR `#39625`. `cleanTokenResult` (which strips `authToken` and returns `PushTokenResult`) and `isPushTokenPOSTProps`/`isPushTokenDELETEProps` validators already exist on `develop`. PR `#39625` only migrates `push.get` and `push.info` to the chained pattern. Do not flag `cleanTokenResult` or `PushTokenResult` as newly introduced behavior-breaking changes when reviewing this PR.
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.
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.
📚 Learning: 2025-11-05T21:04:35.787Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37357
File: ee/packages/federation-matrix/src/setup.ts:103-120
Timestamp: 2025-11-05T21:04:35.787Z
Learning: In Rocket.Chat's federation-matrix setup (ee/packages/federation-matrix/src/setup.ts and apps/meteor/ee/server/startup/federation.ts), configureFederationMatrixSettings does not need to be called before setupFederationMatrix. The SDK's init() establishes infrastructure (database, event handlers, APIs) first, and the configuration can be applied later via settings watchers before actual federation events are processed. The config only matters when events actually occur, at which point all infrastructure is already configured.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.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:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
📚 Learning: 2025-09-19T15:15:04.642Z
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 36991
File: apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts:219-221
Timestamp: 2025-09-19T15:15:04.642Z
Learning: The Federation_Matrix_homeserver_domain setting in apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts is part of the old federation system and is being deprecated/removed, so configuration issues with this setting should not be flagged for improvement.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
📚 Learning: 2025-11-04T16:49:19.107Z
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37377
File: apps/meteor/ee/server/hooks/federation/index.ts:86-88
Timestamp: 2025-11-04T16:49:19.107Z
Learning: In Rocket.Chat's federation system (apps/meteor/ee/server/hooks/federation/), permission checks follow two distinct patterns: (1) User-initiated federation actions (creating rooms, adding users to federated rooms, joining from invites) should throw MeteorError to inform users they lack 'access-federation' permission. (2) Remote server-initiated federation events should silently skip/ignore when users lack permission. The beforeAddUserToRoom hook only executes for local user-initiated actions, so throwing an error there is correct. Remote federation events are handled separately by the federation Matrix package with silent skipping logic.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
📚 Learning: 2025-12-09T20:01:00.324Z
Learnt from: sampaiodiego
Repo: RocketChat/Rocket.Chat PR: 37532
File: ee/packages/federation-matrix/src/FederationMatrix.ts:920-927
Timestamp: 2025-12-09T20:01:00.324Z
Learning: When reviewing federation invite handling in Rocket.Chat (specifically under ee/packages/federation-matrix), understand that rejecting an invite via federationSDK.rejectInvite() triggers an event-driven cleanup: a leave event is emitted and handled by handleLeave() in ee/packages/federation-matrix/src/events/member.ts, which calls Room.performUserRemoval() to remove the subscription. Do not add explicit cleanup in the reject branch of handleInvite(); rely on the existing leave-event flow for cleanup. If making changes, ensure this invariant remains and that any related paths still funnel cleanup through the leave event to avoid duplicate or missing removals.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • ee/packages/federation-matrix/src/FanoutQueue.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • ee/packages/federation-matrix/src/FanoutQueue.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.

Applied to files:

  • ee/packages/federation-matrix/src/FederationMatrix.ts
  • ee/packages/federation-matrix/src/FanoutQueue.ts
🔇 Additional comments (1)
ee/packages/federation-matrix/src/FederationMatrix.ts (1)

118-162: LGTM on the overall avatar update handler logic.

The handler correctly:

  • Skips Matrix-style usernames (line 119)
  • Validates user existence (lines 123-129)
  • Filters out remote federated users using isUserNativeFederated (lines 131-134)
  • Handles undefined/empty avatarETag by setting avatar_url to null (line 140)
  • Uses fanout queue for concurrent, retry-capable per-room profile updates (lines 149-161)

The integration with FanoutQueue aligns well with the PR objective to introduce fanout processing for user avatar update events.

Comment on lines +138 to +140
const matrixUserId = `@${localUser.username}:${this.serverName}`;

const avatarUrl = avatarETag ? `mxc://${this.serverName}/${avatarETag}` : null;
Copy link
Copy Markdown
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
# Search for how avatarETag is used elsewhere in federation context
# and whether there's any avatar upload to Matrix media server

rg -n -C3 'avatarETag|avatar_url|mxc://' ee/packages/federation-matrix/

# Check if there's any media upload logic that might explain this mapping
rg -n -C3 'updateUserProfile|setProfileAvatar' ee/packages/federation-matrix/

Repository: RocketChat/Rocket.Chat

Length of output: 10988


🏁 Script executed:

# Find where the user.avatarUpdate event is emitted and what avatarETag represents
rg -n 'user\.avatarUpdate|avatarETag' --type ts --type js -C 5 | head -100

Repository: RocketChat/Rocket.Chat

Length of output: 7717


🏁 Script executed:

# Look for the event emission in the main Rocket.Chat app to understand avatarETag payload
rg -n 'emit.*user\.avatarUpdate|avatarUpdate.*emit' --type ts --type js -C 3 | head -80

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Search for FileUpload and avatar-related media handling
rg -n 'FileUpload|avatar.*upload|uploadUserFile' --type ts --type js | head -50

Repository: RocketChat/Rocket.Chat

Length of output: 6732


🏁 Script executed:

# Find the event emitter for user.avatarUpdate in the main codebase
rg -n 'user:avatarUpdate|user\.avatarUpdate.*emit|emit.*avatarUpdate' apps/meteor --type ts --type js -C 3

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Look at how avatar changes are handled in the user model or services
rg -n 'setAvatar|updateAvatar|avatarOrigin' apps/meteor --type ts --type js | head -40

Repository: RocketChat/Rocket.Chat

Length of output: 3898


🏁 Script executed:

# Check if there's media upload in the federation context for avatars
rg -n 'uploadMedia|media.*upload|avatar.*matrix' ee/packages/federation-matrix --type ts --type js -C 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Find the event listener registration in FederationMatrix
rg -n 'onEvent|on.*user\.avatarUpdate' ee/packages/federation-matrix/src --type ts -C 5

Repository: RocketChat/Rocket.Chat

Length of output: 10238


🏁 Script executed:

# Look at the FederationMatrix constructor and event setup
rg -n 'class FederationMatrix|constructor' ee/packages/federation-matrix/src/FederationMatrix.ts -A 20

Repository: RocketChat/Rocket.Chat

Length of output: 737


🏁 Script executed:

# Check if there's an events module or what triggers the user.avatarUpdate event
rg -n 'observeUser|avatar.*listener|EventEmitter' ee/packages/federation-matrix/src --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Look at MatrixMediaService to understand what fileId should be
cat ee/packages/federation-matrix/src/services/MatrixMediaService.ts

Repository: RocketChat/Rocket.Chat

Length of output: 3630


🏁 Script executed:

# Search for where user.avatarUpdate event is actually emitted
rg -n 'emit.*user\.avatarUpdate|user\.avatarUpdate.*emit|notifyUser' packages/ apps/meteor --type ts --type js | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 4227


🏁 Script executed:

# Look for file/media handling related to avatars in the codebase
rg -n 'avatarETag.*file|file.*avatarETag|FileUpload.*avatar' --type ts --type js | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 275


🏁 Script executed:

# Find where the user.avatarUpdate event is actually emitted
rg -n 'avatar.*event|avatarUpdate|avatarETag' packages/ apps/meteor/server --type ts --type js -C 2 | grep -A 5 -B 5 'emit\|fire\|trigger'

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Look at FileUpload avatarsOnFinishUpload to understand what gets stored as avatarETag
rg -n 'avatarsOnFinishUpload' --type ts --type js -A 30

Repository: RocketChat/Rocket.Chat

Length of output: 5321


🏁 Script executed:

# Search for where avatarETag is set on the user document
rg -n 'setAvatarData|avatarETag.*=|avatarOrigin' apps/meteor/app/lib --type ts --type js -C 3

Repository: RocketChat/Rocket.Chat

Length of output: 4631


🏁 Script executed:

# Search for the event system that FederationMatrix uses to receive user.avatarUpdate
rg -n 'class.*Service|onEvent.*function|registerListener' ee/packages/federation-matrix/src --type ts | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 313


🏁 Script executed:

# Look for where the notification/event for user.avatarUpdate comes from
rg -n 'user\.avatarUpdate|avatarUpdate' apps/meteor --type ts --type js -B 3 -A 3 | head -80

Repository: RocketChat/Rocket.Chat

Length of output: 3715


🏁 Script executed:

# Check if there's a file ID available that should be used instead
rg -n 'result\._id|fileId|uploadedFile' apps/meteor/app/lib/server/functions/setUserAvatar.ts -C 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


Use file ID instead of avatarETag for MXC URI construction.

The mxc:// URI at line 140 is constructed using avatarETag as the media identifier, but avatarETag is an HTTP ETag (cache validation token from the FileStore upload), not a Matrix media ID. Per the code in setUserAvatar.ts:198, avatarETag = etag || result?.etag || '', which is the HTTP cache header value.

Matrix MXC URIs require a stable media_id from the upload response or file reference. The current implementation will generate invalid MXC URIs that Matrix homeservers cannot resolve, breaking avatar synchronization for federated users.

Either:

  1. Store and pass the actual file ID (from the FileStore upload) instead of the ETag, or
  2. Implement a media upload step to Matrix that returns the proper media_id and store that value instead
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ee/packages/federation-matrix/src/FederationMatrix.ts` around lines 138 -
140, The MXC URI is being built from avatarETag (in FederationMatrix:
matrixUserId/avatarUrl), but avatarETag is an HTTP ETag, not a Matrix media_id;
replace usage of avatarETag with the actual file/media id returned by your
FileStore upload (or perform a media upload to Matrix to obtain media_id) and
construct avatarUrl as `mxc://<serverName>/<media_id>` instead; locate where
avatarETag is read (setUserAvatar.ts and FederationMatrix.ts) and either change
the stored field to the fileId/media_id or call an upload helper (e.g.,
uploadMediaToMatrix or similar) to obtain and persist the correct media_id
before constructing avatarUrl.

@coderabbitai coderabbitai bot removed the type: feature Pull requests that introduces new feature label Mar 29, 2026
Removed TODO comments related to user exclusion, file handling, and common function usage for matrix user ID.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add user avatar update events to a fanout queue

2 participants