From 9ca1847535efa65cbbf83217f0f32a21f7f98f9a Mon Sep 17 00:00:00 2001 From: Dustin Healy <54083382+dustinhealy@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:53:04 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20refactor:=20`customUserVar`=20Er?= =?UTF-8?q?ror=20Normalization=20(#8950)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: localization string had unused template var * fix: add normalizeHttpError to hopefully stop UI hangs when an error is returned in UserController - Ensures updateUserPluginsController always returns valid HTTP status codes instead of undefined - Add normalizeHttpError() helper to safely extract status/message from errors - Default to 400 status code when Error.status is undefined/invalid * refactor: move normalizeHttpError to packages/api --- api/server/controllers/UserController.js | 15 +++++++------- client/src/locales/en/translation.json | 2 +- packages/api/src/utils/http.ts | 26 ++++++++++++++++++++++++ packages/api/src/utils/index.ts | 1 + 4 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 packages/api/src/utils/http.ts diff --git a/api/server/controllers/UserController.js b/api/server/controllers/UserController.js index c91b672724..9912f79ae2 100644 --- a/api/server/controllers/UserController.js +++ b/api/server/controllers/UserController.js @@ -1,5 +1,5 @@ const { logger } = require('@librechat/data-schemas'); -const { webSearchKeys, extractWebSearchEnvVars } = require('@librechat/api'); +const { webSearchKeys, extractWebSearchEnvVars, normalizeHttpError } = require('@librechat/api'); const { getFiles, updateUser, @@ -89,8 +89,8 @@ const updateUserPluginsController = async (req, res) => { if (userPluginsService instanceof Error) { logger.error('[userPluginsService]', userPluginsService); - const { status, message } = userPluginsService; - res.status(status).send({ message }); + const { status, message } = normalizeHttpError(userPluginsService); + return res.status(status).send({ message }); } } @@ -137,7 +137,7 @@ const updateUserPluginsController = async (req, res) => { authService = await updateUserPluginAuth(user.id, keys[i], pluginKey, values[i]); if (authService instanceof Error) { logger.error('[authService]', authService); - ({ status, message } = authService); + ({ status, message } = normalizeHttpError(authService)); } } } else if (action === 'uninstall') { @@ -151,7 +151,7 @@ const updateUserPluginsController = async (req, res) => { `[authService] Error deleting all auth for MCP tool ${pluginKey}:`, authService, ); - ({ status, message } = authService); + ({ status, message } = normalizeHttpError(authService)); } } else { // This handles: @@ -163,7 +163,7 @@ const updateUserPluginsController = async (req, res) => { authService = await deleteUserPluginAuth(user.id, keys[i]); // Deletes by authField name if (authService instanceof Error) { logger.error('[authService] Error deleting specific auth key:', authService); - ({ status, message } = authService); + ({ status, message } = normalizeHttpError(authService)); } } } @@ -193,7 +193,8 @@ const updateUserPluginsController = async (req, res) => { return res.status(status).send(); } - res.status(status).send({ message }); + const normalized = normalizeHttpError({ status, message }); + return res.status(normalized.status).send({ message: normalized.message }); } catch (err) { logger.error('[updateUserPluginsController]', err); return res.status(500).json({ message: 'Something went wrong.' }); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 3c847ade34..e1ec956e9e 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -450,7 +450,7 @@ "com_nav_maximize_chat_space": "Maximize chat space", "com_nav_mcp_configure_server": "Configure {{0}}", "com_nav_mcp_status_connecting": "{{0}} - Connecting", - "com_nav_mcp_vars_update_error": "Error updating MCP custom user variables: {{0}}", + "com_nav_mcp_vars_update_error": "Error updating MCP custom user variables", "com_nav_mcp_vars_updated": "MCP custom user variables updated successfully.", "com_nav_modular_chat": "Enable switching Endpoints mid-conversation", "com_nav_my_files": "My Files", diff --git a/packages/api/src/utils/http.ts b/packages/api/src/utils/http.ts new file mode 100644 index 0000000000..e9f1d62281 --- /dev/null +++ b/packages/api/src/utils/http.ts @@ -0,0 +1,26 @@ +/** + * Normalizes an error-like object into an HTTP status and message. + * Ensures we always respond with a valid numeric status to avoid UI hangs. + */ +export function normalizeHttpError( + err: Error | { status?: number; message?: string } | unknown, + fallbackStatus = 400, +) { + let status = fallbackStatus; + if (err && typeof err === 'object' && 'status' in err && typeof err.status === 'number') { + status = err.status; + } + + let message = 'An error occurred.'; + if ( + err && + typeof err === 'object' && + 'message' in err && + typeof err.message === 'string' && + err.message.length > 0 + ) { + message = err.message; + } + + return { status, message }; +} diff --git a/packages/api/src/utils/index.ts b/packages/api/src/utils/index.ts index db304d4c0f..a513425c70 100644 --- a/packages/api/src/utils/index.ts +++ b/packages/api/src/utils/index.ts @@ -12,3 +12,4 @@ export * from './openid'; export * from './tempChatRetention'; export { default as Tokenizer } from './tokenizer'; export * from './yaml'; +export * from './http';