Skip to content
Ishan Mitra edited this page Jun 6, 2025 · 24 revisions

Welcome to the Rocket.Chat wiki!

This is a personal documentation of my findings while exploring the Rocket.chat core monorepo. Most of it is through my digging and understanding of how everything comes together.

Important: Don't bother looking up the official documentation. Those are for developers looking to create third party apps on top of the Rocket.chat interface or framework. The project I am looking to contribute to currently requires peeking at the Rocket.chat core codebase, especially the Composer component. I am entirely on my own at the moment.

How to add hover over tags

Just wrap <span title="example"></span> around a particular DOM/TSX element and run the yarn dsv command, and on mouse hovering that element, a floating bubble will show "example" on that element. It has been helping me to quickly explore the codebase by "bookmarking" elements.

Initial list of findings

  1. apps/meteor/packages/rocketchat-i18n/i18n/ is a symlink to packages/i18n/src/locales
  2. apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json or packages/i18n/src/locales/en.i18n.json stores all the default English files. Probably the easiest thing to tinker about modifying strings for every existing sentence on RC.
  3. Numbers enclosing in tag structures such as <0></0>, <1></1>, <2></2> and so on, are replaced. Texts within these tags can be thought of as labels. These are not rendered. Sometimes, they are labeled within {{variable}}. Need a better understanding of the react-i18next library.
  4. The react-18next library has been explained visually here.
  5. apps/meteor/client/views/room/composer is the root folder probably containing all the files related to the Message Composer.
  6. apps/meteor/client/views/room/composer/RoomComposer/RoomComposer.tsx is the root component of the Composer. It loads all the {children} components within this component.
  7. apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx:
  • In L403: This code is responsible for the emoji picker
<MessageComposerAction
	icon='emoji'
	disabled={!useEmojis || isRecording || !canSend}
	onClick={handleOpenEmojiPicker}
	title={t('Emoji')}
/>
  • In L409: This code renders the divider between the Emoji picker button and the Bold formatter button
<MessageComposerActionsDivider />
  • In L410: This code renders the formatter buttons Bold, Italics, Strikethrough, Inline code, Multi-Line code, Link and KaTeX
{chat.composer && formatters.length > 0 && (
	MessageBoxFormattingToolbar
		composer={chat.composer}
		variant={sizes.inlineSize < 480 ? 'small' : 'large'}
		items={formatters}
		disabled={isRecording || !canSend}
	/>
)}
  • In L428: This code renders the Submit button. How the button functions needs to be studied. Also disabling this code does not disable the Enter to send hotkey.
<MessageComposerToolbarSubmit>
	{!canSend && (
		<MessageComposerButton primary onClick={onJoin} loading={joinMutation.isPending}>
			{t('Join')}
		</MessageComposerButton>
	)}
	{canSend && (
		<>
			{isEditing && <MessageComposerButton onClick={closeEditing}>{t('Cancel')}</MessageComposerButton>}
			<MessageComposerAction
				aria-label={t('Send')}
				icon='send'
				disabled={!canSend || (!typing && !isEditing)}
				onClick={handleSendMessage}
				secondary={typing || isEditing}
				info={typing || isEditing}
			/>
		</>
	)}
</MessageComposerToolbarSubmit>
  • In L161: When logging the parameters sent to onSend(), text holds the value of the user input in the Composer textbox, tshow is undefined, previewUrls is undefined and isSlashCommandAllowed is true
const handleSendMessage = useEffectEvent(() => {
	const text = chat.composer?.text ?? '';
	chat.composer?.clear();
	popup.clear();

	// console.log("Executing handleSendMessage");
	// console.log("Text:", text);
	// console.log("tshow:", tshow);
	// console.log("previewUrls:", previewUrls);
	// console.log("isSlashCommandAllowed:", isSlashCommandAllowed);

	onSend?.({
		value: text,
		tshow,
		previewUrls,
		isSlashCommandAllowed,
	});
});
  1. apps/meteor/client/views/room/composer/ComposerMessage.tsx:
  • From L44 to L67: Modifying the code as shown below to accept a hardcoded message proves that the onSend function is defined here and MessageBox.tsx is imported in L11
onSend: async ({
	value: _text,
	tshow,
	previewUrls,
	isSlashCommandAllowed,
}: {
	value: string;
	tshow?: boolean;
	previewUrls?: string[];
	isSlashCommandAllowed?: boolean;
}): Promise<void> => {
	try {
		await chat?.action.stop('typing');
		const hardcodedMessage = "This is a hardcoded message!";

		const newMessageSent = await chat?.flows.sendMessage({
			text: hardcodedMessage,
			tshow,
			previewUrls,
			isSlashCommandAllowed,
		});
		if (newMessageSent) onSend?.();
	} catch (error) {
		dispatchToastMessage({ type: 'error', message: error });
	}
},
  1. apps/meteor/client/lib/chats/ChatAPI.ts
  • From L146: There is a definition for sendMessage, but I do not think it is part of the stack call.
readonly flows: {
	readonly uploadFiles: (files: readonly File[], resetFileInput?: () => void) => Promise<void>;
	readonly sendMessage: ({
		text,
		tshow,
	}: {
		text: string;
		tshow?: boolean;
		previewUrls?: string[];
		isSlashCommandAllowed?: boolean;
	}) => Promise<boolean>;
	readonly processSlashCommand: (message: IMessage, userId: string | null) => Promise<boolean>;
	readonly processTooLongMessage: (message: IMessage) => Promise<boolean>;
	readonly processMessageEditing: (
			message: Pick<IMessage, '_id' | 't'> & Partial<Omit<IMessage, '_id' | 't'>>,
			previewUrls?: string[],
		) => Promise<boolean>;
		readonly processSetReaction: (message: Pick<IMessage, 'msg'>) => Promise<boolean>;
		readonly requestMessageDeletion: (message: IMessage) => Promise<void>;
		readonly replyBroadcast: (message: IMessage) => Promise<void>;
	};
  1. apps/meteor/app/ui/client/lib/ChatMessages.ts
  • In L14: sendMessage is imported
import { sendMessage } from '../../../../client/lib/chats/flows/sendMessage';
  • In L178: sendMessage binding function is present
this.flows = {
	uploadFiles: uploadFiles.bind(null, this),
	sendMessage: sendMessage.bind(this, this),
	processSlashCommand: processSlashCommand.bind(null, this),
	processTooLongMessage: processTooLongMessage.bind(null, this),
	processMessageEditing: processMessageEditing.bind(null, this),
	processSetReaction: processSetReaction.bind(null, this),
	requestMessageDeletion: requestMessageDeletion.bind(this, this),
	replyBroadcast: replyBroadcast.bind(null, this),
};
  1. apps/meteor/client/lib/chats/flows/sendMessage.ts
  • L38: The sendMessage function is declared here. Executing a console.trace() confirms the stack call.
export const sendMessage = async (
	chat: ChatAPI,
	{
		text,
		tshow,
		previewUrls,
		isSlashCommandAllowed,
	}: { text: string; tshow?: boolean; previewUrls?: string[]; isSlashCommandAllowed?: boolean },
): Promise<boolean> => {
	console.trace("sendMessage function called with: text: ", text, ", tshow: ", tshow, ", previewUrls: ", previewUrls, ", isSlashCommandAllowed: ", isSlashCommandAllowed);
.
.
.
}
  1. apps/meteor/app/ui-message/client/messageBox/createComposerAPI.ts
  • L317: text is defined as a getter function and NOT a variable. The input.value returns santized text from the textarea component.
get text(): string {
	return input.value;
},

Clone this wiki locally