diff --git a/src/features/Apiexplorer/RequestResponseRenderer/index.tsx b/src/features/Apiexplorer/RequestResponseRenderer/index.tsx index b40df632..30601daa 100644 --- a/src/features/Apiexplorer/RequestResponseRenderer/index.tsx +++ b/src/features/Apiexplorer/RequestResponseRenderer/index.tsx @@ -9,6 +9,7 @@ import styles from '../RequestJSONBox/RequestJSONBox.module.scss'; import { ValidDialog } from '../ValidDialog'; import { translate } from '@docusaurus/Translate'; import { Button } from '@deriv-com/quill-ui'; +import { hasDuplicateKeys } from '@site/src/utils'; export interface IResponseRendererProps { name: T; @@ -33,17 +34,25 @@ function RequestResponseRenderer({ disableApiNameOnRequest(); }, []); + const setInvalidJson = () => { + setIsNotValid(true); + setToggleModal(false); + }; + const parseRequestJSON = () => { let request_data: TSocketRequestProps extends never ? undefined : TSocketRequestProps; try { + if (hasDuplicateKeys(reqData)) { + setInvalidJson(); + return; + } request_data = JSON.parse(reqData); + return request_data; } catch (error) { - setIsNotValid(true); - setToggleModal(false); + setInvalidJson(); + return; } - - return request_data; }; const handleClick = useCallback(() => { @@ -51,9 +60,12 @@ function RequestResponseRenderer({ setToggleModal(true); return; } - clear(); - send(parseRequestJSON()); - setResponseState(true); + const request_data = parseRequestJSON(); + if (request_data) { + clear(); + send(request_data); + setResponseState(true); + } }, [reqData, send, clear, auth, is_logged_in]); const handleClear = () => { diff --git a/src/features/Apiexplorer/SubscribeRenderer/index.tsx b/src/features/Apiexplorer/SubscribeRenderer/index.tsx index 036999c5..56505b06 100644 --- a/src/features/Apiexplorer/SubscribeRenderer/index.tsx +++ b/src/features/Apiexplorer/SubscribeRenderer/index.tsx @@ -12,6 +12,7 @@ import PlaygroundSection from '../RequestResponseRenderer/PlaygroundSection'; import LoginDialog from '../LoginDialog'; import ValidDialog from '../ValidDialog'; import { translate } from '@docusaurus/Translate'; +import { hasDuplicateKeys } from '@site/src/utils'; export interface IResponseRendererProps { name: T; @@ -54,17 +55,26 @@ function SubscribeRenderer({ if (is_switching_account) unsubscribe(); }, [is_switching_account]); + const setInvalidJson = () => { + setIsNotValid(true); + setToggleModal(false); + }; + const parseRequestJSON = useCallback(() => { let request_data: TSocketRequestProps extends never ? undefined : TSocketRequestProps; try { + if (hasDuplicateKeys(reqData)) { + setInvalidJson(); + return; + } + request_data = JSON.parse(reqData); + return request_data; } catch (error) { - setIsNotValid(true); - setToggleModal(false); + setInvalidJson(); + return; } - - return request_data; }, [reqData]); const handleClick = useCallback(() => { @@ -72,9 +82,12 @@ function SubscribeRenderer({ setToggleModal(true); return; } - if (is_subscribed) unsubscribe(); - subscribe(parseRequestJSON()); - setResponseState(true); + const request_data = parseRequestJSON(); + if (request_data) { + if (is_subscribed) unsubscribe(); + subscribe(request_data); + setResponseState(true); + } }, [parseRequestJSON, subscribe, auth]); const handleClear = () => { diff --git a/src/utils/index.ts b/src/utils/index.ts index 22ce085a..f2d9f9f2 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -240,6 +240,22 @@ export const findVirtualAccount = (accounts: IUserLoginAccount[]) => { return accounts.find((item) => item.name.includes('VRTC')); }; +export const hasDuplicateKeys = (jsonStr: string) => { + const keyCounts = new Map(); + const regex = /"([^"]+)"\s*:/g; + let match; + + while ((match = regex.exec(jsonStr)) !== null) { + const key = match[1]; + if (keyCounts.has(key)) { + return true; // Found a duplicate + } + keyCounts.set(key, 1); + } + + return false; // No duplicates found +}; + /** * Returns the appropriate TMB config URL based on the environment * @returns {string} The TMB config URL