RocketChat / RocketChat/Rocket.Chat
Livechat widget: `normalizeMessages()` async predicate in `Array.filter` is a no-op — thread-reply quoted context never renders
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 46.1k
- Forks
- 13.9k
- Avg merge
- 3d 3h
- Merged PRs (30d)
- 130
Description
Description:
normalizeMessages() in packages/livechat/src/lib/threads.ts passes an async callback to Array.prototype.filter:
export const normalizeMessages = (messages: any[] = []): Promise<any[]> =>
Promise.all(
messages.filter(async (message) => {
const result = await normalizeMessage(message);
return result;
}),
);
Array.filter's predicate must be synchronous. An async function always returns a Promise, which is truthy, so filter keeps every message unchanged - normalizeMessage() never actually runs its intended logic. normalizeMessage() is meant to:
- drop thread-reply messages already registered as parents, and
- attach
threadMsg/ quoted-parent data to reply messages soMessage/index.tsxcan render a "replying to" preview bubble.
Because the filter is a no-op, thread replies shown in the Livechat widget render as flat standalone messages with no quoted-parent context.
This was introduced (and self-flagged) in #41559 during the TS migration - the PR left a FIXME comment noting it as a known latent bug surfaced by the type checker, to be "revisited separately," but it was never filed or fixed.
Steps to reproduce:
- Open the Livechat widget on a site with Livechat enabled.
- Start a conversation and reply in a thread from the agent side.
- Observe the reply as it renders in the visitor-facing widget message list.
Expected behavior:
Thread-reply messages should show a quoted preview of the parent message they're replying to (as Message/index.tsx is designed to render via threadMsg).
Actual behavior:
Thread replies render as plain, unannotated messages - no parent-message preview - because normalizeMessages() never filters/annotates the message array as intended.
Proposed fix:
export const normalizeMessages = async (messages: any[] = []): Promise<any[]> => {
const normalized = await Promise.all(messages.map(normalizeMessage));
return normalized.filter(Boolean);
};
This preserves normalizeMessage's existing contract (return null/falsy to drop a message) and needs no changes to callers (packages/livechat/src/lib/room.ts:256,290).
Server version:
N/A - client-side bug in the @rocket.chat/livechat widget package, reproducible on current develop.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/livechat/src/lib/threads.ts by reading normalizeMessage and normalizeMessages, then check their callers in packages/livechat/src/lib/room.ts at lines 256 and 290. Confirm the normalized result preserves intended filtering and threadMsg data, and verify that Message/index.tsx renders quoted parent context for thread replies.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100