fix: API Modernization, Security, and Stability Improvements for EmbeddedChat#1155
fix: API Modernization, Security, and Stability Improvements for EmbeddedChat#1155vivekyadav-3 wants to merge 20 commits intoRocketChat:developfrom
Conversation
…ecording consistency
…ChatBody, and fix emoji insertion at cursor
… authentication, commands, and message tools
- Added encodeURIComponent() to properly encode user input before appending to URL query string - Prevents special characters (&, ?, #, %) from breaking query parameters - Fixes issue RocketChat#1149
- Changed typing status timeout from 15000ms to 10000ms - Makes typing indicator more responsive and updates faster - Improves real-time chat experience
- Added defensive check to ensure selectedItem exists before accessing properties - Prevents TypeError when user types commands not in the filtered list - Fixes issue RocketChat#1144
…icated - Added default empty string values in destructuring pattern - Fixes all 37 API methods that were sending literal 'undefined' as header values - Headers now send empty strings instead of 'undefined' when user is not logged in - Fixes issue RocketChat#1133
Summary of changes: - Replaced legacy DDP method calls (getUserRoles, rooms:get) with modern REST API endpoints for better server compatibility. - Fixed critical busy-wait loop in handleTypingEvent that caused application freezes. - URL-encoded search and filter parameters in API calls to prevent HTTP Parameter Pollution. - Added a 50,000-character safety guard in sendMessage to prevent crashes from excessively large messages. - Cleaned up unused variables and imports across several components.
There was a problem hiding this comment.
Pull request overview
This PR modernizes parts of EmbeddedChat’s React UI and the EmbeddedChatApi client to improve stability, responsiveness, and request safety (URL encoding + safer request bodies), while also cleaning up unused imports/variables.
Changes:
- Refactors typing indicator listener handling and reduces typing timeout (intended) while adding memoization/cleanup in several UI components.
- Updates API calls to newer REST endpoints, adds URL encoding for query/field/search params, and replaces manual JSON string construction with
JSON.stringify. - Adds UX-facing error toasts for auth flows and general cleanup across multiple components/stores.
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/views/ChatInput/ChatInput.js | Adjusts typing timeout + quote-link assembly logic |
| packages/react/src/views/EmbeddedChat.js | Adds auto-login failure toast + memoizes auth config |
| packages/react/src/views/ChatHeader/ChatHeader.js | Refactors logout to always delete stored token and reset state |
| packages/api/src/EmbeddedChatApi.ts | Removes blocking typing lock, encodes query params, modernizes room/role APIs, uses JSON.stringify, adds max-length guard |
| packages/react/src/lib/emoji.js | Updates emoji parsing to replace all shortnames |
| packages/react/lint_report.txt | Adds a lint output artifact file (should not be committed) |
| GSOC_2026_PROPOSAL_EmbeddedChat.md | Adds proposal document (process/docs addition) |
| packages/react/src/views/* (multiple) | Memoization, unused import cleanup, minor safety checks |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const { deleteToken } = getTokenStorage( | ||
| ECOptions?.secure | ||
| ); |
There was a problem hiding this comment.
Logout token deletion is wired to getTokenStorage(ECOptions?.secure), but ECOptions created in EmbeddedChat does not include secure. In secure mode this can delete from the wrong storage (or not delete at all), leaving auth tokens behind after logout. Pass secure through ECOptions or obtain the correct deleteToken from the same place the RCInstance was initialized.
| const { deleteToken } = getTokenStorage( | |
| ECOptions?.secure | |
| ); | |
| const secure = useSettingsStore((state) => state.secure); | |
| const { deleteToken } = getTokenStorage(secure); |
| if (messageObj.msg && messageObj.msg.length > 50000) { | ||
| return { success: false, error: "Message is too long" }; | ||
| } |
There was a problem hiding this comment.
The hard-coded 50000 message length limit is a magic number and can diverge from the server’s configured Message_MaxAllowedSize (which the UI already fetches). Consider using the server-provided limit (or at least centralizing this constant) and returning an error shape consistent with other API failures so the UI can surface the reason to the user.
packages/react/src/lib/emoji.js
Outdated
| return text.replace(/:([^:\s]+):/g, (match) => { | ||
| const unicode = emojione.shortnameToUnicode(match); | ||
| return unicode !== undefined && unicode !== match ? unicode : match; | ||
| }); |
There was a problem hiding this comment.
This implementation violates ESLint arrow-body-style (it uses a block body with an explicit return). If the repo enforces this rule (as shown in the included lint output), adjust the callback to use an implicit return or disable the rule for this line/file.
| return text.replace(/:([^:\s]+):/g, (match) => { | |
| const unicode = emojione.shortnameToUnicode(match); | |
| return unicode !== undefined && unicode !== match ? unicode : match; | |
| }); | |
| return text.replace( | |
| /:([^:\s]+):/g, | |
| (match) => | |
| emojione.shortnameToUnicode(match) !== undefined && | |
| emojione.shortnameToUnicode(match) !== match | |
| ? emojione.shortnameToUnicode(match) | |
| : match | |
| ); |
| timerRef.current = setTimeout(() => { | ||
| typingRef.current = false; | ||
| }, [15000]); | ||
| }, [10000]); |
There was a problem hiding this comment.
setTimeout is being called with an array ([10000]) as the delay argument. This will coerce to NaN and effectively make the typing flag reset immediately, breaking typing status behavior. Pass a number (e.g., 10000) instead of an array.
| }, [10000]); | |
| }, 10000); |
| @@ -125,13 +132,17 @@ const EmbeddedChat = (props) => { | |||
| try { | |||
| await RCInstance.autoLogin(auth); | |||
| } catch (error) { | |||
| console.error(error); | |||
| console.error('Auto-login failed:', error); | |||
| dispatchToastMessage({ | |||
| type: 'error', | |||
| message: 'Auto-login failed. Please sign in manually.', | |||
| }); | |||
| } finally { | |||
| setIsLoginIn(false); | |||
| } | |||
| }; | |||
| autoLogin(); | |||
| }, [RCInstance, auth, setIsLoginIn]); | |||
| }, [RCInstance, auth, setIsLoginIn, dispatchToastMessage]); | |||
There was a problem hiding this comment.
useToastBarDispatch() is called in EmbeddedChat before the <ToastBarProvider> is rendered (the provider is created inside this component’s return). If there is no higher-level provider, this hook will throw at runtime. Consider moving ToastBarProvider above this component, or moving the auto-login/toast logic into a child component that is rendered inside the provider.
| async getRoomInfo() { | ||
| try { | ||
| const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; | ||
| const { userId = "", authToken = "" } = | ||
| (await this.auth.getCurrentUser()) || {}; | ||
| const response = await fetch( | ||
| `${this.host}/api/v1/method.call/rooms%3Aget`, | ||
| `${this.host}/api/v1/rooms.get`, | ||
| { | ||
| body: JSON.stringify({ | ||
| message: JSON.stringify({ | ||
| msg: "method", | ||
| id: null, | ||
| method: "rooms/get", | ||
| params: [], | ||
| }), | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| "X-Auth-Token": authToken, | ||
| "X-User-Id": userId, | ||
| }, | ||
| method: "POST", | ||
| method: "GET", | ||
| } | ||
| ); | ||
|
|
||
| const result = await response.json(); | ||
|
|
||
| if (result.success && result.message) { | ||
| const parsedMessage = JSON.parse(result.message); | ||
| return parsedMessage; | ||
| if (result.success && result.update) { | ||
| return { success: true, result: result.update }; | ||
| } | ||
| return null; |
There was a problem hiding this comment.
getRoomInfo() now calls GET /api/v1/rooms.get without filtering by roomId, and returns the entire update array. Current usage expects data for the current room, so this can lead to incorrect room details (and unnecessary payload). Consider calling an endpoint that returns a single room (or filter result.update to the current this.rid before returning).
| # GSoC 2026 Proposal: EmbeddedChat Stability & Input Hardening - Vivek Yadav | ||
|
|
||
| --- | ||
|
|
||
| ## 1. Abstract | ||
|
|
||
| I am proposing a targeted set of improvements for the **Rocket.Chat EmbeddedChat** component to ensure production-grade reliability. While EmbeddedChat serves as a powerful drop-in solution, specific user experience gaps—specifically in message composition and authentication stability—hinder its adoption. My project will leverage the **React SDK** internals to harden the input handling system, optimize the authentication hooks, and implement a robust "quoting" mechanism. | ||
|
|
||
| ## 2. The Problem | ||
|
|
There was a problem hiding this comment.
This file contains a personal/organizational proposal document rather than project documentation tied to shipped behavior. Keeping it in the main repo may add noise and make maintenance harder; consider moving it to an external document or linking it from an issue/PR discussion instead of committing it here.
| const auth = useMemo( | ||
| () => authProp, | ||
| [JSON.stringify(authProp)] // Deep comparison via stringify to handle inline objects | ||
| ); |
There was a problem hiding this comment.
Using useMemo(() => authProp, [JSON.stringify(authProp)]) forces a stringify on every render and can still be unstable (key order) while also defeating exhaustive-deps linting. Prefer passing a stable auth object from the caller, or use a dedicated deep-compare memo/effect helper so dependency tracking remains predictable.
I’ve been working on a few improvements to help make the chat experience more reliable and secure. These changes focus on fixing some underlying performance bottlenecks and modernizing how the app communicates with the Rocket.Chat server.
Key Changes
Resolved App Freezing: I identified a blocking loop in the typing status handler that was causing the browser to freeze when multiple people were active at once. I’ve refactored this logic to be non-blocking, ensuring the UI stays smooth and responsive.
Modernized Server Communication: Some of the older methods used to fetch user roles and room details are becoming unreliable on modern server versions. I’ve updated these to use the latest REST API endpoints. This fixes issues where "Admin" badges or archived room information didn't always display correctly.
Enhanced Security & Reliability: I’ve implemented proper encoding for search and message filters to prevent "parameter pollution" and ensure requests don't fail unexpectedly. I also added a safety check to handle excessively long messages gracefully before they are sent.
General Housekeeping: I did some minor cleanup across several components, removing unused variables and imports to keep the codebase tidy and maintainable.