diff --git a/src/routes/safe/components/Apps/ManageApps.js b/src/routes/safe/components/Apps/ManageApps.js index 972477ab..acec7f35 100644 --- a/src/routes/safe/components/Apps/ManageApps.js +++ b/src/routes/safe/components/Apps/ManageApps.js @@ -13,6 +13,7 @@ import GnoForm from '~/components/forms/GnoForm' import { required } from '~/components/forms/validator' import Img from '~/components/layout/Img' import appsIconSvg from '~/routes/safe/components/Transactions/TxsTable/TxType/assets/appsIcon.svg' +import { isValid as isURLValid } from '~/utils/url' const FORM_ID = 'add-apps-form' @@ -51,9 +52,7 @@ type Props = { } const urlValidator = (value: string) => { - return /(?:^|[ \t])((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/gm.test(value) - ? undefined - : 'Please, provide a valid url' + return isURLValid(value) ? undefined : 'Please, provide a valid url' } const composeValidatorsApps = (...validators: Function[]): FieldValidator => (value: Field, values, meta) => { @@ -92,7 +91,15 @@ const ManageApps = ({ appList, onAppAdded, onAppToggle }: Props) => { } const uniqueAppValidator = (value) => { - const exists = appList.find((a) => a.url === value.trim()) + const exists = appList.find((a) => { + try { + const currentUrl = new URL(a.url) + const newUrl = new URL(value) + return currentUrl.href === newUrl.href + } catch (error) { + return 'There was a problem trying to validate the URL existence.' + } + }) return exists ? 'This app is already registered.' : undefined } diff --git a/src/routes/safe/components/Apps/utils.js b/src/routes/safe/components/Apps/utils.js index 70965b29..defed2fc 100644 --- a/src/routes/safe/components/Apps/utils.js +++ b/src/routes/safe/components/Apps/utils.js @@ -29,40 +29,45 @@ export const getAppInfoFromUrl = async (appUrl: string) => { return res } - let cleanedUpAppUrl = appUrl.trim() - if (cleanedUpAppUrl.substr(-1) === '/') { - cleanedUpAppUrl = cleanedUpAppUrl.substr(0, cleanedUpAppUrl.length - 1) - res.url = cleanedUpAppUrl + res.url = appUrl.trim() + let noTrailingSlashUrl = res.url + if (noTrailingSlashUrl.substr(-1) === '/') { + noTrailingSlashUrl = noTrailingSlashUrl.substr(0, noTrailingSlashUrl.length - 1) } try { - const appInfo = await axios.get(`${cleanedUpAppUrl}/manifest.json`) + const appInfo = await axios.get(`${noTrailingSlashUrl}/manifest.json`) // verify imported app fulfil safe requirements if (!appInfo || !appInfo.data || !appInfo.data.name || !appInfo.data.description) { throw Error('The app does not fulfil the structure required.') } + // the DB origin field has a limit of 100 characters + const originFieldSize = 100 + const jsonDataLength = 20 + const remainingSpace = originFieldSize - res.url.length - jsonDataLength + res = { ...res, ...appInfo.data, - id: JSON.stringify({ url: cleanedUpAppUrl, name: appInfo.data.name }), + id: JSON.stringify({ url: res.url, name: appInfo.data.name.substring(0, remainingSpace) }), error: false, } + if (appInfo.data.iconPath) { try { - const iconInfo = await axios.get(`${cleanedUpAppUrl}/${appInfo.data.iconPath}`) + const iconInfo = await axios.get(`${noTrailingSlashUrl}/${appInfo.data.iconPath}`, { timeout: 1000 * 10 }) if (/image\/\w/gm.test(iconInfo.headers['content-type'])) { - res.iconUrl = `${cleanedUpAppUrl}/${appInfo.data.iconPath}` + res.iconUrl = `${noTrailingSlashUrl}/${appInfo.data.iconPath}` } } catch (error) { - console.error(`It was not possible to fetch icon from app ${cleanedUpAppUrl}`) + console.error(`It was not possible to fetch icon from app ${res.url}`) } } - return res } catch (error) { - console.error(`It was not possible to fetch app from ${cleanedUpAppUrl}: ${error.message}`) + console.error(`It was not possible to fetch app from ${res.url}: ${error.message}`) return res } }