Skip to content

Conversation

diegolmello
Copy link
Member

@diegolmello diegolmello commented Oct 13, 2025

Proposed changes

Sends the extra params needed for chat.update to work.

Issue(s)

Counterpart of RocketChat/Rocket.Chat#37138
https://rocketchat.atlassian.net/browse/ESH-47

How to test or reproduce

Tested on workspaces with and without RocketChat/Rocket.Chat#37138.
The app doesn't crash.

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • New Features

    • You can now edit end‑to‑end encrypted messages.
    • Message updates support encrypted content and end‑to‑end mentions.
    • Decrypted message text is automatically shown in the message body when available.
  • Bug Fixes

    • Ensures the text of decrypted messages appears correctly after edits or decryption, improving readability and consistency.

Copy link
Contributor

coderabbitai bot commented Oct 13, 2025

Walkthrough

This change replaces the previous encrypted message content type with a new union type across message and attachment definitions, updates the chat.update REST params to accept encrypted content and e2e mentions, adjusts decryption to populate message text, and updates editMessage to handle encryption and branch requests accordingly.

Changes

Cohort / File(s) Summary
Type refactor: encrypted content and mentions
app/definitions/IMessage.ts, app/definitions/IAttachment.ts
Introduces versioned encrypted content types and exported TEncryptedContent; replaces IMessageE2EEContent with TEncryptedContent in message and attachment definitions; adds e2eMentions to IMessage; updates imports accordingly.
REST API: chat.update payload
app/definitions/rest/v1/chat.ts
Extends chat.update POST params: text becomes optional; new optional fields content?: TEncryptedContent and e2eMentions?: IMessage['e2eMentions'].
Encryption/decryption handling
app/lib/encryption/room.ts
After decrypting rc.v1.aes-sha2 content, if content.text exists, assigns it to message.msg.
Service: editMessage flow
app/lib/services/restApi.ts
Expands editMessage signature to include content and e2eMentions; performs encryption and throws if it fails; if encryption yields content, calls chat.update with content and e2eMentions, else falls back to text.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Client UI
  participant Service as editMessage()
  participant Crypto as E2E Encryptor
  participant API as REST chat.update

  UI->>Service: editMessage({ id, rid, msg?, content?, e2eMentions? })
  Note over Service: Prepare payload for encryption
  Service->>Crypto: encrypt({ msg/content, rid, mentions })
  alt Encryption fails
    Crypto-->>Service: error/null
    Service-->>UI: throw Error("encryption failed")
  else Encryption succeeds
    Crypto-->>Service: { content? , text? , e2eMentions? }
    alt Has encrypted content
      Service->>API: POST /chat.update { roomId, msgId, content, e2eMentions }
    else Fallback to plaintext
      Service->>API: POST /chat.update { roomId, msgId, text }
    end
    API-->>Service: 200 OK
    Service-->>UI: resolve()
  end
Loading
sequenceDiagram
  autonumber
  participant Server as Server
  participant Decrypt as room.decrypt()
  participant Msg as IMessage

  Server-->>Decrypt: content (rc.v1.aes-sha2)
  Decrypt-->>Decrypt: decrypt content
  alt content.text present
    Decrypt->>Msg: msg = content.text
  else No text
    Note over Decrypt,Msg: msg unchanged
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–70 minutes

Suggested reviewers

  • OtavioStasiak

Poem

A nibble of bytes, a hop through the night,
I bundle secrets snug and tight.
Mentions tucked in, content anew,
I tap my paws—encrypt on cue.
Update sent with whisker’d grace,
Decrypt, then words fall into place. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning While the PR correctly enhances chat.update support for encrypted content, it also introduces broader changes to IAttachment.ts and IMessage.ts type definitions and adds decryption normalization in room.ts that are not directly required by the linked issue’s objective. Please separate the chat.update API changes from the unrelated type and decryption updates by moving those definition and room.ts modifications into a dedicated PR, so each change set remains focused on its specific concern.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately and concisely describes the primary change of fixing the chat.update method’s handling of end-to-end encrypted messages by sending the required content parameter, making it clear and specific.
Linked Issues Check ✅ Passed The PR fully implements ESH-47 by extending the chat.update REST definition to accept content and e2eMentions parameters and updating the editMessage function to pass these values, thereby enabling proper updates of encrypted messages.
✨ 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.chat-update

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.

@diegolmello diegolmello marked this pull request as ready for review October 13, 2025 18:59
@diegolmello diegolmello requested a deployment to official_android_build October 13, 2025 19:01 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to experimental_ios_build October 13, 2025 19:01 — with GitHub Actions Waiting
@diegolmello diegolmello requested a deployment to experimental_android_build October 13, 2025 19:01 — with GitHub Actions Waiting
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/lib/encryption/room.ts (1)

648-651: Normalize from both content.text and content.msg.

Be explicit and resilient to either field being present.

-					if (content.text) {
-						message.msg = content.text;
-					}
+					const normalizedText = (content as any)?.text ?? (content as any)?.msg;
+					if (normalizedText) {
+						message.msg = normalizedText;
+					}
app/definitions/IMessage.ts (1)

83-107: Consider a single discriminated union without the base interface.

Defining TEncryptedContent directly tightens the algorithm literal type and reduces indirection. Current approach works; this is just simplification.

export type TEncryptedContent =
  | { algorithm: 'rc.v1.aes-sha2'; ciphertext: string }
  | { algorithm: 'rc.v2.aes-sha2'; ciphertext: string; iv: string; kid: string };
📜 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 53e26da and 3970e12.

📒 Files selected for processing (5)
  • app/definitions/IAttachment.ts (2 hunks)
  • app/definitions/IMessage.ts (3 hunks)
  • app/definitions/rest/v1/chat.ts (2 hunks)
  • app/lib/encryption/room.ts (1 hunks)
  • app/lib/services/restApi.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/lib/services/restApi.ts (2)
app/definitions/IMessage.ts (1)
  • IMessage (146-177)
android/app/src/main/java/chat/rocket/reactnative/notification/Encryption.java (1)
  • Encryption (67-267)
app/definitions/rest/v1/chat.ts (2)
app/definitions/IRoom.ts (1)
  • IServerRoom (152-236)
app/definitions/IMessage.ts (2)
  • TEncryptedContent (107-107)
  • IMessage (146-177)
app/definitions/IAttachment.ts (1)
app/definitions/IMessage.ts (1)
  • TEncryptedContent (107-107)
⏰ 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: ESLint and Test / run-eslint-and-test
🔇 Additional comments (4)
app/definitions/IMessage.ts (1)

171-171: Public e2eMentions shape looks good.

No concerns. Aligns with usage downstream.

app/definitions/rest/v1/chat.ts (1)

78-84: API shape update LGTM; confirm backward compatibility.

Extending chat.update to accept content/e2eMentions makes sense. Please verify behavior against older servers where text might still be required.

app/definitions/IAttachment.ts (2)

2-2: Type import update LGTM.

Consistent with TEncryptedContent adoption.


75-76: Attachment content now uses TEncryptedContent — good.

Matches decryption logic that checks algorithm and ciphertext.

Comment on lines +966 to 988
export const editMessage = async (message: Pick<IMessage, 'id' | 'msg' | 'rid' | 'content' | 'e2eMentions'>) => {
const result = await Encryption.encryptMessage(message as IMessage);
if (!result) {
throw new Error('Failed to encrypt message');
}

if (result.content) {
// RC 0.49.0
return sdk.post('chat.update', {
roomId: message.rid,
msgId: message.id,
content: result.content,
e2eMentions: result.e2eMentions
});
}

// RC 0.49.0
return sdk.post('chat.update', { roomId: rid, msgId: message.id, text: msg });
return sdk.post('chat.update', {
roomId: message.rid,
msgId: message.id,
text: message.msg || ''
});
};
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

Add fallback for servers that don’t support content in chat.update.

Currently, edits in E2EE rooms may fail on older servers (where text is required). Try content first, then fallback to text with the ciphertext.

 export const editMessage = async (message: Pick<IMessage, 'id' | 'msg' | 'rid' | 'content' | 'e2eMentions'>) => {
   const result = await Encryption.encryptMessage(message as IMessage);
   if (!result) {
     throw new Error('Failed to encrypt message');
   }

   if (result.content) {
-    // RC 0.49.0
-    return sdk.post('chat.update', {
-      roomId: message.rid,
-      msgId: message.id,
-      content: result.content,
-      e2eMentions: result.e2eMentions
-    });
+    try {
+      // Prefer new API with encrypted content
+      return await sdk.post('chat.update', {
+        roomId: message.rid,
+        msgId: message.id,
+        content: result.content,
+        e2eMentions: result.e2eMentions
+      });
+    } catch (e) {
+      // Fallback for older servers: send ciphertext as text
+      const ciphertext = (result as any).msg ?? result.content.ciphertext ?? message.msg ?? '';
+      return sdk.post('chat.update', {
+        roomId: message.rid,
+        msgId: message.id,
+        text: ciphertext
+      });
+    }
   }

   // RC 0.49.0
   return sdk.post('chat.update', {
     roomId: message.rid,
     msgId: message.id,
     text: message.msg || ''
   });
 };
📝 Committable suggestion

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

Suggested change
export const editMessage = async (message: Pick<IMessage, 'id' | 'msg' | 'rid' | 'content' | 'e2eMentions'>) => {
const result = await Encryption.encryptMessage(message as IMessage);
if (!result) {
throw new Error('Failed to encrypt message');
}
if (result.content) {
// RC 0.49.0
return sdk.post('chat.update', {
roomId: message.rid,
msgId: message.id,
content: result.content,
e2eMentions: result.e2eMentions
});
}
// RC 0.49.0
return sdk.post('chat.update', { roomId: rid, msgId: message.id, text: msg });
return sdk.post('chat.update', {
roomId: message.rid,
msgId: message.id,
text: message.msg || ''
});
};
export const editMessage = async (message: Pick<IMessage, 'id' | 'msg' | 'rid' | 'content' | 'e2eMentions'>) => {
const result = await Encryption.encryptMessage(message as IMessage);
if (!result) {
throw new Error('Failed to encrypt message');
}
if (result.content) {
try {
// Prefer new API with encrypted content
return await sdk.post('chat.update', {
roomId: message.rid,
msgId: message.id,
content: result.content,
e2eMentions: result.e2eMentions
});
} catch (e) {
// Fallback for older servers: send ciphertext as text
const ciphertext = (result as any).msg ?? result.content.ciphertext ?? message.msg ?? '';
return sdk.post('chat.update', {
roomId: message.rid,
msgId: message.id,
text: ciphertext
});
}
}
// RC 0.49.0
return sdk.post('chat.update', {
roomId: message.rid,
msgId: message.id,
text: message.msg || ''
});
};

export const editMessage = async (message: Pick<IMessage, 'id' | 'msg' | 'rid'>) => {
const { rid, msg } = await Encryption.encryptMessage(message as IMessage);
export const editMessage = async (message: Pick<IMessage, 'id' | 'msg' | 'rid' | 'content' | 'e2eMentions'>) => {
const result = await Encryption.encryptMessage(message as IMessage);
Copy link
Contributor

Choose a reason for hiding this comment

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

I tested it on the Mobile workspace (7.9.0), and editMessage doesn’t work, encryptMessage always returns content and fails on chat.update.
Screenshot 2025-10-14 at 17 30 33

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants