Conversation
|
Looks like this PR is ready to merge! 🎉 |
🦋 Changeset detectedLatest commit: 6269156 The changes in this PR will be included in the next version bump. This PR includes changesets to release 41 packages
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 |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review infoConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
WalkthroughAdds model-based existence checks to custom-sounds and emoji-custom endpoints before streaming files, centralizes an SVG fallback for missing custom emojis with 304 handling, and adds a changeset declaring a patch fixing cross-resource access when using FileSystem storage mode. (34 words) Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant Server as Server (custom-sounds / emoji-custom)
participant DB as DB (CustomSounds / EmojiCustom)
participant Store as FileStore (FileSystem)
Client->>Server: GET /custom-sounds/:name or /emoji-custom/:name
Server->>DB: lookup metadata (findOneById / findOneByName)
DB-->>Server: metadata (found / not found)
alt metadata found
Server->>Store: attempt to read file stream
Store-->>Server: file stream (found / not found)
alt file found
Server-->>Client: 200 + file stream + content-type/caching headers
else file missing
Server-->>Client: 404 Not Found
end
else metadata missing (emoji)
Server-->>Client: 200 + fallback SVG or 304
else metadata missing (sound)
Server-->>Client: 404 Not Found
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #38531 +/- ##
===========================================
- Coverage 70.64% 70.63% -0.02%
===========================================
Files 3189 3189
Lines 112715 112712 -3
Branches 20363 20438 +75
===========================================
- Hits 79627 79611 -16
- Misses 31044 31052 +8
- Partials 2044 2049 +5
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/meteor/app/emoji-custom/server/startup/emoji-custom.js (1)
43-50:⚠️ Potential issue | 🟡 Minor
split('.').pop()returns the full filename—not an empty string—for extension-less emoji names.For
params.emoji = "emojiname"(no dot),"emojiname".split('.').pop()is"emojiname", which is truthy, so the!extensionguard passes. The file'scontentTypecheck at line 56 prevents serving non-image bytes, so there's no security gap, but such filenames bypass the intent of the guard and ultimately fall through to a hardcodedimage/jpegContent-Type.Consider a stricter guard that also rejects filenames that don't contain a dot (i.e., have no recognizable extension):
🛡️ Proposed tightened guard
- const extension = params.emoji?.split('.').pop()?.toLowerCase() ?? ''; - - if (!extension) { + const lastDot = params.emoji?.lastIndexOf('.'); + const extension = lastDot != null && lastDot >= 0 ? params.emoji.slice(lastDot + 1).toLowerCase() : ''; + + if (!extension || lastDot === 0) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js` around lines 43 - 50, The guard that derives extension using params.emoji?.split('.').pop() incorrectly treats extension-less filenames like "emojiname" as valid; change the check that sets extension so it only accepts names containing a dot and a non-empty suffix (e.g., ensure params.emoji includes('.') before splitting or use a regex to extract the part after the last dot and verify it's different from the full name), and if no valid dot-extension is found, respond with 403 as the current branch intends; update the logic around the extension variable (the code that currently sets extension) and the subsequent 403 branch to enforce this stricter validation.apps/meteor/app/custom-sounds/server/startup/custom-sounds.js (1)
50-57:⚠️ Potential issue | 🟠 MajorBackward-compatibility risk: audio files without
contentTypemetadata will silently 404.When
file.contentTypeisundefined, the expressionfile.contentType?.startsWith('audio/')evaluates toundefined(falsy), causing the guard!file || !file.contentType?.startsWith('audio/')to returntrueand serve a 404 for valid audio files that lack stored MIME type metadata.This is a real issue for GridFS-backed files uploaded without explicit
contentTypemetadata—thegetFileWithReadStreamimplementation extractscontentType: file.contentTypefrom database records, which can beundefined. While the FileSystem adapter mitigates this viamime.lookup()with an'application/octet-stream'fallback, GridFS has no such guarantee. Files from pre-fix uploads may lack contentType entirely, breaking backward compatibility.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.js` around lines 50 - 57, The current guard in the custom sounds route rejects files where file.contentType is undefined; update the check in the handler that calls RocketChatFileCustomSoundsInstance.getFileWithReadStream(fileId) to treat undefined contentType as unknown rather than non-audio: if contentType is missing, infer the MIME type from file.name (use the existing mime.lookup utility or equivalent) and only 404 when the resolved mime type does not startWith('audio/'); when streaming, set the response Content-Type to the inferred value or fallback to 'application/octet-stream' to preserve GridFS/backward compatibility.
🧹 Nitpick comments (1)
apps/meteor/app/emoji-custom/server/startup/emoji-custom.js (1)
101-107: Usefile.contentTypefor the responseContent-Typeheader instead of deriving it from the URL extension.After the check at line 56 already validates that
file.contentType.startsWith('image/'), the actualContent-Typeheader is set by re-deriving the type from the URL extension. If the storedcontentTypeand the URL extension diverge (e.g., a file stored asemoji.svgwithcontentType: 'image/png'), the browser receives aContent-Type: image/svg+xmlheader while streaming PNG bytes, causing a render failure.custom-sounds.jscorrectly usesfile.contentTypedirectly at line 84—applying the same pattern here is simpler and eliminates the mismatch:♻️ Proposed fix
- if (/^svg$/i.test(extension)) { - res.setHeader('Content-Type', 'image/svg+xml'); - } else if (/^png$/i.test(extension)) { - res.setHeader('Content-Type', 'image/png'); - } else { - res.setHeader('Content-Type', 'image/jpeg'); - } + res.setHeader('Content-Type', file.contentType);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js` around lines 101 - 107, The response Content-Type is being derived from the URL extension; change it to use the stored MIME type instead by replacing the extension-based branch with a single call that sets res.setHeader('Content-Type', file.contentType) (the variable file and res are available in this scope and file.contentType was already validated to start with "image/"); update the header-setting code in emoji-custom.js where the extension checks occur to use file.contentType so the streamed bytes match the declared MIME type.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.changeset/clean-ears-fly.mdapps/meteor/app/custom-sounds/server/startup/custom-sounds.jsapps/meteor/app/emoji-custom/server/startup/emoji-custom.js
🧰 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:
apps/meteor/app/custom-sounds/server/startup/custom-sounds.jsapps/meteor/app/emoji-custom/server/startup/emoji-custom.js
🔇 Additional comments (1)
.changeset/clean-ears-fly.md (1)
1-5: LGTM — changeset description is accurate and clearly scoped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.js`:
- Around line 50-57: The current guard in the custom sounds route rejects files
where file.contentType is undefined; update the check in the handler that calls
RocketChatFileCustomSoundsInstance.getFileWithReadStream(fileId) to treat
undefined contentType as unknown rather than non-audio: if contentType is
missing, infer the MIME type from file.name (use the existing mime.lookup
utility or equivalent) and only 404 when the resolved mime type does not
startWith('audio/'); when streaming, set the response Content-Type to the
inferred value or fallback to 'application/octet-stream' to preserve
GridFS/backward compatibility.
In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js`:
- Around line 43-50: The guard that derives extension using
params.emoji?.split('.').pop() incorrectly treats extension-less filenames like
"emojiname" as valid; change the check that sets extension so it only accepts
names containing a dot and a non-empty suffix (e.g., ensure params.emoji
includes('.') before splitting or use a regex to extract the part after the last
dot and verify it's different from the full name), and if no valid dot-extension
is found, respond with 403 as the current branch intends; update the logic
around the extension variable (the code that currently sets extension) and the
subsequent 403 branch to enforce this stricter validation.
---
Nitpick comments:
In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js`:
- Around line 101-107: The response Content-Type is being derived from the URL
extension; change it to use the stored MIME type instead by replacing the
extension-based branch with a single call that sets
res.setHeader('Content-Type', file.contentType) (the variable file and res are
available in this scope and file.contentType was already validated to start with
"image/"); update the header-setting code in emoji-custom.js where the extension
checks occur to use file.contentType so the streamed bytes match the declared
MIME type.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (all 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="apps/meteor/app/emoji-custom/server/startup/emoji-custom.js">
<violation number="1" location="apps/meteor/app/emoji-custom/server/startup/emoji-custom.js:59">
P2: The content-type validation was removed here, so non-image files can now be served through /emoji-custom when an emoji name exists. Since uploads accept arbitrary content types, this can return videos/other binaries as emoji responses and sets an incorrect image Content-Type. Reintroduce the contentType guard to keep non-image uploads from being served.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/meteor/app/custom-sounds/server/startup/custom-sounds.js (1)
51-61:⚠️ Potential issue | 🟠 Major
file.readStreamis leaked whensoundis missing butfileis non-null.Same pattern as the emoji endpoint:
Promise.allopens the file stream before the DB check completes. In the cross-resource scenario thegetFileWithReadStreamcall can successfully open a stream for a file that exists on the shared filesystem whileCustomSounds.findOneByIdreturns null—resulting in a 404 early return with a live, undrainedreadStream.🐛 Proposed fix: destroy the stream on early return
- if (!file || !sound) { + if (!file || !sound) { + file?.readStream?.destroy(); res.writeHead(404); res.write('Not found'); res.end(); return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.js` around lines 51 - 61, The current Promise.all opens the file stream via RocketChatFileCustomSoundsInstance.getFileWithReadStream before verifying DB absence from CustomSounds.findOneById, which can leak file.readStream when sound is null; update the handler so that if sound is missing but file is non-null you call file.readStream.destroy() (or file.readStream.close() if appropriate) before writing the 404 response and returning, ensuring you reference the same RocketChatFileCustomSoundsInstance.getFileWithReadStream result and CustomSounds.findOneById check to locate where to insert the stream cleanup.
🧹 Nitpick comments (4)
apps/meteor/app/emoji-custom/server/startup/emoji-custom.js (3)
104-110: Content-Type is still derived from the URL extension rather thanfile.contentType.
custom-sounds.jsline 88 usesfile.contentTypedirectly from the stored record, which reflects the actual MIME type of the uploaded file. The emoji endpoint hardcodes a mapping (svg → image/svg+xml,png → image/png, otherwiseimage/jpeg) based purely on the URL extension. Given the PR's goal of backward-compatible content validation, usingfile.contentTypehere would also correctly handle emojis uploaded with non-standard extensions or no extension.♻️ Proposed refactor
- if (/^svg$/i.test(params.emoji.split('.').pop())) { - res.setHeader('Content-Type', 'image/svg+xml'); - } else if (/^png$/i.test(params.emoji.split('.').pop())) { - res.setHeader('Content-Type', 'image/png'); - } else { - res.setHeader('Content-Type', 'image/jpeg'); - } + res.setHeader('Content-Type', file.contentType);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js` around lines 104 - 110, Replace the URL-extension based Content-Type logic in the emoji endpoint with the stored file's MIME type: read and use the uploaded record's file.contentType (the same property used in custom-sounds.js) to set res.setHeader('Content-Type', file.contentType); if file.contentType is missing or empty, fall back to a sensible default (e.g., 'image/jpeg') to preserve compatibility; keep references to params.emoji only for lookup, not for deriving the MIME type.
52-53: Consider replacingcountByNameOrAliaswith a lighter existence check for consistency.
countByNameOrAliasexecutes a count aggregation and returns a number, but the result is only used as a boolean (!emojiCount). The custom-sounds endpoint usesfindOneByIdwith{ projection: { _id: 1 } }, which fetches a single minimal document and is a more idiomatic existence check. A parallelfindOneByNameOrAlias(if available on the model) with the same minimal projection would be slightly more efficient and symmetric.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js` around lines 52 - 53, Replace the count-based existence check by using a minimal-document find: replace the call to EmojiCustom.countByNameOrAlias(...) with EmojiCustom.findOneByNameOrAlias(params.emoji.split('.')[0], { projection: { _id: 1 } }) (or implement/find an equivalent finder), update the destructured variable name from emojiCount to something like emojiDoc (or emojiExistsDoc), and change the boolean check from !emojiCount to !emojiDoc to preserve behavior; this mirrors the custom-sounds pattern and avoids a full count aggregation.
52-55: Wrap thePromise.allbody in atry/catchto prevent response hangs on DB/IO errors.
EmojiCustom.countByNameOrAliasis a new DB operation. If it (orgetFileWithReadStream) throws—e.g., on a MongoDB connection error—the rejected promise from the async handler is unhandled byWebApp.connectHandlers. Errors thrown inside async functions without a catch block result in unhandled promise rejections, which in Node.js 3.x+ can terminate the process, and in all versions leave the response socket open and the client hanging.♻️ Proposed fix
return WebApp.connectHandlers.use('/emoji-custom/', async (req, res /* , next*/) => { + try { const params = { emoji: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')) }; // ... rest of handler ... file.readStream.pipe(res); + } catch (err) { + SystemLogger.error({ msg: 'Error serving emoji-custom', err }); + if (!res.headersSent) { + res.writeHead(500); + res.end(); + } + } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js` around lines 52 - 55, The Promise.all call that awaits EmojiCustom.countByNameOrAlias and RocketChatFileEmojiCustomInstance.getFileWithReadStream must be wrapped in a try/catch inside the WebApp.connectHandlers async handler to avoid unhandled rejections; catch the error, log it (using the existing logger or console.error), send an HTTP 500 response (res.writeHead(500) / res.end with a short message) and ensure the response is closed so the client doesn't hang, then return early. Locate the await Promise.all([...]) containing EmojiCustom.countByNameOrAlias and RocketChatFileEmojiCustomInstance.getFileWithReadStream and wrap it with this error handling.apps/meteor/app/custom-sounds/server/startup/custom-sounds.js (1)
51-54: Add atry/catchto handle DB/IO errors in the async handler.
CustomSounds.findOneByIdis a new async DB operation; if it (orgetFileWithReadStream) throws, the unhandled rejection from the async middleware handler leaves the response hanging for the client.♻️ Proposed fix
return WebApp.connectHandlers.use('/custom-sounds/', async (req, res /* , next*/) => { + try { const fileId = decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, '')); // ... rest of handler ... file.readStream.pipe(res); + } catch (err) { + SystemLogger.error({ msg: 'Error serving custom-sound', err }); + if (!res.headersSent) { + res.writeHead(500); + res.end(); + } + } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.js` around lines 51 - 54, The Promise.all call invoking CustomSounds.findOneById and RocketChatFileCustomSoundsInstance.getFileWithReadStream can throw and must be wrapped in a try/catch inside the async request handler; add a try/catch around the await Promise.all([CustomSounds.findOneById(...), RocketChatFileCustomSoundsInstance.getFileWithReadStream(...)]) to log the error (use the existing logger) and return a proper HTTP error response to the client (e.g., send a 500 with a short message or call the handler's next(err)), so the request does not hang when fileId DB/IO operations fail.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/meteor/app/custom-sounds/server/startup/custom-sounds.jsapps/meteor/app/emoji-custom/server/startup/emoji-custom.js
🧰 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:
apps/meteor/app/emoji-custom/server/startup/emoji-custom.jsapps/meteor/app/custom-sounds/server/startup/custom-sounds.js
🧠 Learnings (2)
📓 Common learnings
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 37547
File: packages/i18n/src/locales/en.i18n.json:634-634
Timestamp: 2025-11-19T12:32:29.696Z
Learning: Repo: RocketChat/Rocket.Chat
Context: i18n workflow
Learning: In this repository, new translation keys should be added to packages/i18n/src/locales/en.i18n.json only; other locale files are populated via the external translation pipeline and/or fall back to English. Do not request adding the same key to all locale files in future reviews.
Learnt from: MartinSchoeler
Repo: RocketChat/Rocket.Chat PR: 37408
File: apps/meteor/client/views/admin/ABAC/useRoomAttributeOptions.tsx:53-69
Timestamp: 2025-11-10T19:06:20.146Z
Learning: In the Rocket.Chat repository, do not provide suggestions or recommendations about code sections marked with TODO comments. The maintainers have already identified these as future work and external reviewers lack the full context about implementation plans and timing.
📚 Learning: 2026-02-10T16:32:49.806Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:49.806Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON which automatically deserializes Date objects. Stream handlers (e.g., sdk.stream()) receive Date fields as Date objects directly and do not require manual conversion using `new Date()`. Only REST API responses require manual date conversion because they return plain JSON where dates are serialized as strings.
Applied to files:
apps/meteor/app/emoji-custom/server/startup/emoji-custom.js
🧬 Code graph analysis (2)
apps/meteor/app/emoji-custom/server/startup/emoji-custom.js (1)
packages/models/src/index.ts (1)
EmojiCustom(148-148)
apps/meteor/app/custom-sounds/server/startup/custom-sounds.js (1)
packages/models/src/index.ts (1)
CustomSounds(144-144)
⏰ 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). (4)
- GitHub Check: 📦 Build Packages
- GitHub Check: cubic · AI code reviewer
- GitHub Check: CodeQL-Build
- GitHub Check: CodeQL-Build
🔇 Additional comments (1)
apps/meteor/app/custom-sounds/server/startup/custom-sounds.js (1)
51-61: The core security fix (parallel fetch + DB-record guard) looks correct.
CustomSounds.findOneById(fileId.split('.')[0], { projection: { _id: 1 } })is a lightweight existence check that correctly blocks cross-resource access: a request to/custom-sounds/<emoji-name>will find no CustomSounds record and short-circuit to 404 before the file bytes are streamed. Usingfile.contentTypefor theContent-Typeheader (line 88) is also the right approach — it reflects the actual MIME type regardless of filename extension.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js`:
- Around line 52-59: The concurrent Promise.all creates a live readStream from
RocketChatFileEmojiCustomInstance.getFileWithReadStream even when
EmojiCustom.countByNameOrAlias is zero or when a 304 is returned; ensure you
explicitly destroy/close file.readStream before any early return: in the guard
that checks if (!file || !emojiCount) and in the cache-validation branch that
returns 304, call file.readStream.destroy() (or file.readStream.close() if using
a different stream API) if file and file.readStream exist, then proceed to send
the SVG placeholder or 304 response; reference the functions
EmojiCustom.countByNameOrAlias,
RocketChatFileEmojiCustomInstance.getFileWithReadStream and the file.readStream
object when applying the fix.
---
Outside diff comments:
In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.js`:
- Around line 51-61: The current Promise.all opens the file stream via
RocketChatFileCustomSoundsInstance.getFileWithReadStream before verifying DB
absence from CustomSounds.findOneById, which can leak file.readStream when sound
is null; update the handler so that if sound is missing but file is non-null you
call file.readStream.destroy() (or file.readStream.close() if appropriate)
before writing the 404 response and returning, ensuring you reference the same
RocketChatFileCustomSoundsInstance.getFileWithReadStream result and
CustomSounds.findOneById check to locate where to insert the stream cleanup.
---
Nitpick comments:
In `@apps/meteor/app/custom-sounds/server/startup/custom-sounds.js`:
- Around line 51-54: The Promise.all call invoking CustomSounds.findOneById and
RocketChatFileCustomSoundsInstance.getFileWithReadStream can throw and must be
wrapped in a try/catch inside the async request handler; add a try/catch around
the await Promise.all([CustomSounds.findOneById(...),
RocketChatFileCustomSoundsInstance.getFileWithReadStream(...)]) to log the error
(use the existing logger) and return a proper HTTP error response to the client
(e.g., send a 500 with a short message or call the handler's next(err)), so the
request does not hang when fileId DB/IO operations fail.
In `@apps/meteor/app/emoji-custom/server/startup/emoji-custom.js`:
- Around line 104-110: Replace the URL-extension based Content-Type logic in the
emoji endpoint with the stored file's MIME type: read and use the uploaded
record's file.contentType (the same property used in custom-sounds.js) to set
res.setHeader('Content-Type', file.contentType); if file.contentType is missing
or empty, fall back to a sensible default (e.g., 'image/jpeg') to preserve
compatibility; keep references to params.emoji only for lookup, not for deriving
the MIME type.
- Around line 52-53: Replace the count-based existence check by using a
minimal-document find: replace the call to EmojiCustom.countByNameOrAlias(...)
with EmojiCustom.findOneByNameOrAlias(params.emoji.split('.')[0], { projection:
{ _id: 1 } }) (or implement/find an equivalent finder), update the destructured
variable name from emojiCount to something like emojiDoc (or emojiExistsDoc),
and change the boolean check from !emojiCount to !emojiDoc to preserve behavior;
this mirrors the custom-sounds pattern and avoids a full count aggregation.
- Around line 52-55: The Promise.all call that awaits
EmojiCustom.countByNameOrAlias and
RocketChatFileEmojiCustomInstance.getFileWithReadStream must be wrapped in a
try/catch inside the WebApp.connectHandlers async handler to avoid unhandled
rejections; catch the error, log it (using the existing logger or
console.error), send an HTTP 500 response (res.writeHead(500) / res.end with a
short message) and ensure the response is closed so the client doesn't hang,
then return early. Locate the await Promise.all([...]) containing
EmojiCustom.countByNameOrAlias and
RocketChatFileEmojiCustomInstance.getFileWithReadStream and wrap it with this
error handling.
Proposed changes (including videos or screenshots)
I propose fetching the file first and validating the
contentTypeattribute matches the kind of the requested resource (e.g image, audio). I thought about validating by file extensions (mp3 for audio, png for emojis), but that might lead to scenarios where previous files with different extensions where uploaded because at a certain point in Rocket.Chat they were allowed, and if we don't include them then when the user fetches them, they'll get an error. So I determined this is the most backwards-compatible approach.Edit: The approach was changed to checking whether the file exists in the collection, and if it does, then it's tried to read it and if one of these two operations fail, then
Not foundis returned.Why weren't tests added?
Test were not added since they are blocked because of the impossibility of changing the storage type for sounds/emojis at runtime. A task was created for this: CORE-1843. You will notice this task is blocked by another one that consists of making the storage type reactive to changes (currently, we can't change the storage type setting in tests because to make it take effect we must restart the server).
Issue(s)
CORE-1805 Improve how custom-sounds and emoji-custom endpoints get files
Steps to test or reproduce
Further comments
Summary by CodeRabbit
Bug Fixes
Chores