From e48d2ce3cbc697671c136349cf989f80fbd23167 Mon Sep 17 00:00:00 2001 From: Talha Mahmood <36226639+Talha345@users.noreply.github.com> Date: Fri, 11 Aug 2023 17:48:40 +0200 Subject: [PATCH 1/4] Link Navigation fixed and URL polyfill added. --- package-lock.json | 58 +++++ package.json | 1 + src/App.js | 1 + src/CONFIG.js | 2 +- .../HTMLRenderers/AnchorRenderer.js | 12 +- src/libs/Url.js | 71 +----- tests/unit/UrlTest.js | 222 +----------------- 7 files changed, 84 insertions(+), 283 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f23fe160a6b..0442a2f99603 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,6 +95,7 @@ "react-native-screens": "3.21.0", "react-native-svg": "^13.9.0", "react-native-tab-view": "^3.5.2", + "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", "react-native-vision-camera": "^2.15.4", "react-native-web-lottie": "^1.4.4", @@ -42895,6 +42896,17 @@ "react-native-pager-view": "*" } }, + "node_modules/react-native-url-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz", + "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==", + "dependencies": { + "whatwg-url-without-unicode": "8.0.0-3" + }, + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/react-native-view-shot": { "version": "3.6.0", "license": "MIT", @@ -49460,6 +49472,27 @@ "node": ">=12" } }, + "node_modules/whatwg-url-without-unicode": { + "version": "8.0.0-3", + "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", + "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==", + "dependencies": { + "buffer": "^5.4.3", + "punycode": "^2.1.1", + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "engines": { + "node": ">=8" + } + }, "node_modules/which": { "version": "2.0.2", "license": "ISC", @@ -79265,6 +79298,14 @@ "use-latest-callback": "^0.1.5" } }, + "react-native-url-polyfill": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz", + "integrity": "sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==", + "requires": { + "whatwg-url-without-unicode": "8.0.0-3" + } + }, "react-native-view-shot": { "version": "3.6.0", "requires": {} @@ -83554,6 +83595,23 @@ "webidl-conversions": "^7.0.0" } }, + "whatwg-url-without-unicode": { + "version": "8.0.0-3", + "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", + "integrity": "sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==", + "requires": { + "buffer": "^5.4.3", + "punycode": "^2.1.1", + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==" + } + } + }, "which": { "version": "2.0.2", "requires": { diff --git a/package.json b/package.json index 0b5f9d34936f..1e10168055dc 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "react-native-screens": "3.21.0", "react-native-svg": "^13.9.0", "react-native-tab-view": "^3.5.2", + "react-native-url-polyfill": "^2.0.0", "react-native-view-shot": "^3.6.0", "react-native-vision-camera": "^2.15.4", "react-native-web-lottie": "^1.4.4", diff --git a/src/App.js b/src/App.js index d8faa911f86b..278a72175f00 100644 --- a/src/App.js +++ b/src/App.js @@ -22,6 +22,7 @@ import ThemeProvider from './styles/themes/ThemeProvider'; import ThemeStylesProvider from './styles/ThemeStylesProvider'; import {CurrentReportIDContextProvider} from './components/withCurrentReportID'; import {EnvironmentProvider} from './components/withEnvironment'; +import 'react-native-url-polyfill/auto'; import * as Session from './libs/actions/Session'; // For easier debugging and development, when we are in web we expose Onyx to the window, so you can more easily set data into Onyx diff --git a/src/CONFIG.js b/src/CONFIG.js index c5825203db09..e96afec914ac 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -80,7 +80,7 @@ export default { }, CAPTURE_METRICS: lodashGet(Config, 'CAPTURE_METRICS', 'false') === 'true', ONYX_METRICS: lodashGet(Config, 'ONYX_METRICS', 'false') === 'true', - DEV_PORT: process.env.PORT || 8080, + DEV_PORT: process.env.PORT || 8082, E2E_TESTING: lodashGet(Config, 'E2E_TESTING', 'false') === 'true', SEND_CRASH_REPORTS: lodashGet(Config, 'SEND_CRASH_REPORTS', 'false') === 'true', IS_USING_WEB_PROXY: getPlatform() === 'web' && useWebProxy, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index f7086486637d..b439428789be 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -15,19 +15,23 @@ import AnchorForAttachmentsOnly from '../../AnchorForAttachmentsOnly'; import * as Url from '../../../libs/Url'; import ROUTES from '../../../ROUTES'; import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot'; +import useEnvironment from '../../../hooks/useEnvironment'; function AnchorRenderer(props) { const htmlAttribs = props.tnode.attributes; - + const {environmentURL} = useEnvironment(); // An auth token is needed to download Expensify chat attachments const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); const displayName = lodashGet(props.tnode, 'domNode.children[0].data', ''); const parentStyle = lodashGet(props.tnode, 'parent.styles.nativeTextRet', {}); const attrHref = htmlAttribs.href || ''; - const attrPath = lodashGet(Url.getURLObject(attrHref), 'path', '').replace('/', ''); + const attrPath = Url.getPathFromURL(attrHref); + const hasSameOrigin = Url.hasSameExpensifyOrigin(attrHref, environmentURL); const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(attrHref, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(attrHref, CONFIG.EXPENSIFY.STAGING_API_ROOT); const internalNewExpensifyPath = - (Url.hasSameExpensifyOrigin(attrHref, CONST.NEW_EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(attrHref, CONST.STAGING_NEW_EXPENSIFY_URL)) && + (Url.hasSameExpensifyOrigin(attrHref, CONST.NEW_EXPENSIFY_URL) || + Url.hasSameExpensifyOrigin(attrHref, CONST.STAGING_NEW_EXPENSIFY_URL) || + attrHref.startsWith(CONST.DEV_NEW_EXPENSIFY_URL)) && !CONST.PATHS_TO_TREAT_AS_EXTERNAL.includes(attrPath) ? attrPath : ''; @@ -48,7 +52,7 @@ function AnchorRenderer(props) { // If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation // instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag) - if (internalNewExpensifyPath) { + if (internalNewExpensifyPath && hasSameOrigin) { Navigation.navigate(internalNewExpensifyPath); return; } diff --git a/src/libs/Url.js b/src/libs/Url.js index 7e7c230de95f..7965a4043be7 100644 --- a/src/libs/Url.js +++ b/src/libs/Url.js @@ -1,5 +1,3 @@ -import {URL_WEBSITE_REGEX} from 'expensify-common/lib/Url'; - /** * Add / to the end of any URL if not present * @param {String} url @@ -13,76 +11,33 @@ function addTrailingForwardSlash(url) { } /** - * Parse href to URL object - * @param {String} href - * @returns {Object} - */ -function getURLObject(href) { - const urlRegex = new RegExp(URL_WEBSITE_REGEX, 'gi'); - let match; - try { - if (!href.startsWith('mailto:')) { - match = urlRegex.exec(href); - } - } catch (e) { - // eslint-disable-next-line no-console - console.warn('Error parsing url in Url.getURLObject', {error: e}); - } - if (!match) { - return { - href: undefined, - protocol: undefined, - hostname: undefined, - path: undefined, - }; - } - const baseUrl = match[0]; - const protocol = match[1]; - return { - href, - protocol, - hostname: baseUrl.replace(protocol, ''), - path: href.startsWith(baseUrl) ? href.replace(baseUrl, '') : '', - }; -} - -/** - * Determine if we should remove w3 from hostname - * E.g www.expensify.com should be the same as expensify.com - * @param {String} hostname - * @returns {Boolean} + * Get path from URL string + * @param {String} url + * @returns {String} */ -function shouldRemoveW3FromExpensifyUrl(hostname) { - // Since expensify.com.dev is accessible with and without www subdomain - if (hostname === 'www.expensify.com.dev') { - return true; - } - const parts = hostname.split('.').reverse(); - const subDomain = parts[2]; - return subDomain === 'www'; +function getPathFromURL(url) { + const path = new URL(url).pathname; + return path.substring(1); // Remove the leading '/' } /** * Determine if two urls have the same origin - * Just care about expensify url to avoid the second-level domain (www.example.co.uk) * @param {String} url1 * @param {String} url2 * @returns {Boolean} */ function hasSameExpensifyOrigin(url1, url2) { - const host1 = getURLObject(url1).hostname; - const host2 = getURLObject(url2).hostname; - if (!host1 || !host2) { - return false; - } - const host1WithoutW3 = shouldRemoveW3FromExpensifyUrl(host1) ? host1.replace('www.', '') : host1; - const host2WithoutW3 = shouldRemoveW3FromExpensifyUrl(host2) ? host2.replace('www.', '') : host2; - return host1WithoutW3 === host2WithoutW3; + const removeW3 = (host) => host.replace(/^www\./i, ''); + + const parsedUrl1 = new URL(url1); + const parsedUrl2 = new URL(url2); + + return removeW3(parsedUrl1.host) === removeW3(parsedUrl2.host); } export { // eslint-disable-next-line import/prefer-default-export addTrailingForwardSlash, hasSameExpensifyOrigin, - getURLObject, + getPathFromURL, }; diff --git a/tests/unit/UrlTest.js b/tests/unit/UrlTest.js index ea6460e24ca9..8ed674803f4f 100644 --- a/tests/unit/UrlTest.js +++ b/tests/unit/UrlTest.js @@ -1,221 +1,6 @@ const Url = require('../../src/libs/Url'); describe('Url', () => { - describe('getURLObject()', () => { - it('It should work correctly', () => { - expect(Url.getURLObject('foo.com')).toEqual({ - href: 'foo.com', - protocol: undefined, - hostname: 'foo.com', - path: '', - }); - expect(Url.getURLObject('www.foo.com')).toEqual({ - href: 'www.foo.com', - protocol: undefined, - hostname: 'www.foo.com', - path: '', - }); - expect(Url.getURLObject('http://www.foo.com')).toEqual({ - href: 'http://www.foo.com', - protocol: 'http://', - hostname: 'www.foo.com', - path: '', - }); - expect(Url.getURLObject('http://foo.com/blah_blah')).toEqual({ - href: 'http://foo.com/blah_blah', - protocol: 'http://', - hostname: 'foo.com', - path: '/blah_blah', - }); - expect(Url.getURLObject('http://foo.com/blah_blah_(wikipedia)')).toEqual({ - href: 'http://foo.com/blah_blah_(wikipedia)', - protocol: 'http://', - hostname: 'foo.com', - path: '/blah_blah_(wikipedia)', - }); - expect(Url.getURLObject('http://www.example.com/wpstyle/?p=364')).toEqual({ - href: 'http://www.example.com/wpstyle/?p=364', - protocol: 'http://', - hostname: 'www.example.com', - path: '/wpstyle/?p=364', - }); - expect(Url.getURLObject('https://www.example.com/foo/?bar=baz&inga=42&quux')).toEqual({ - href: 'https://www.example.com/foo/?bar=baz&inga=42&quux', - protocol: 'https://', - hostname: 'www.example.com', - path: '/foo/?bar=baz&inga=42&quux', - }); - expect(Url.getURLObject('http://foo.com/(something)?after=parens')).toEqual({ - href: 'http://foo.com/(something)?after=parens', - protocol: 'http://', - hostname: 'foo.com', - path: '/(something)?after=parens', - }); - expect(Url.getURLObject('http://code.google.com/events/#&product=browser')).toEqual({ - href: 'http://code.google.com/events/#&product=browser', - protocol: 'http://', - hostname: 'code.google.com', - path: '/events/#&product=browser', - }); - expect(Url.getURLObject('http://foo.bar/?q=Test%20URL-encoded%20stuff')).toEqual({ - href: 'http://foo.bar/?q=Test%20URL-encoded%20stuff', - protocol: 'http://', - hostname: 'foo.bar', - path: '/?q=Test%20URL-encoded%20stuff', - }); - expect(Url.getURLObject('http://www.test.com/path?param=123#123')).toEqual({ - href: 'http://www.test.com/path?param=123#123', - protocol: 'http://', - - hostname: 'www.test.com', - path: '/path?param=123#123', - }); - expect(Url.getURLObject('http://1337.net')).toEqual({ - href: 'http://1337.net', - protocol: 'http://', - - hostname: '1337.net', - path: '', - }); - expect(Url.getURLObject('http://a.b-c.de/')).toEqual({ - href: 'http://a.b-c.de/', - protocol: 'http://', - - hostname: 'a.b-c.de', - path: '/', - }); - expect(Url.getURLObject('https://sd1.sd2.docs.google.com/')).toEqual({ - href: 'https://sd1.sd2.docs.google.com/', - protocol: 'https://', - hostname: 'sd1.sd2.docs.google.com', - path: '/', - }); - expect(Url.getURLObject('https://expensify.cash/#/r/1234')).toEqual({ - href: 'https://expensify.cash/#/r/1234', - protocol: 'https://', - hostname: 'expensify.cash', - path: '/#/r/1234', - }); - expect(Url.getURLObject('https://github.com/Expensify/ReactNativeChat/pull/6.45')).toEqual({ - href: 'https://github.com/Expensify/ReactNativeChat/pull/6.45', - protocol: 'https://', - hostname: 'github.com', - path: '/Expensify/ReactNativeChat/pull/6.45', - }); - expect(Url.getURLObject('https://github.com/Expensify/Expensify/issues/143,231')).toEqual({ - href: 'https://github.com/Expensify/Expensify/issues/143,231', - protocol: 'https://', - hostname: 'github.com', - path: '/Expensify/Expensify/issues/143,231', - }); - expect(Url.getURLObject('testRareTLDs.beer')).toEqual({ - href: 'testRareTLDs.beer', - protocol: undefined, - hostname: 'testRareTLDs.beer', - path: '', - }); - expect(Url.getURLObject('test@expensify.com')).toEqual({ - href: 'test@expensify.com', - protocol: undefined, - hostname: 'expensify.com', - path: '', - }); - expect(Url.getURLObject('test.completelyFakeTLD')).toEqual({ - href: undefined, - protocol: undefined, - hostname: undefined, - path: undefined, - }); - expect( - Url.getURLObject( - // eslint-disable-next-line max-len - 'https://www.expensify.com/_devportal/tools/logSearch/#query=request_id:(%22Ufjjim%22)+AND+timestamp:[2021-01-08T03:48:10.389Z+TO+2021-01-08T05:48:10.389Z]&index=logs_expensify-008878)', - ), - ).toEqual({ - // eslint-disable-next-line max-len - href: 'https://www.expensify.com/_devportal/tools/logSearch/#query=request_id:(%22Ufjjim%22)+AND+timestamp:[2021-01-08T03:48:10.389Z+TO+2021-01-08T05:48:10.389Z]&index=logs_expensify-008878)', - protocol: 'https://', - hostname: 'www.expensify.com', - path: '/_devportal/tools/logSearch/#query=request_id:(%22Ufjjim%22)+AND+timestamp:[2021-01-08T03:48:10.389Z+TO+2021-01-08T05:48:10.389Z]&index=logs_expensify-008878)', - }); - expect(Url.getURLObject('http://necolas.github.io/react-native-web/docs/?path=/docs/components-pressable--disabled ')).toEqual({ - href: 'http://necolas.github.io/react-native-web/docs/?path=/docs/components-pressable--disabled ', - protocol: 'http://', - hostname: 'necolas.github.io', - path: '/react-native-web/docs/?path=/docs/components-pressable--disabled ', - }); - expect(Url.getURLObject('https://github.com/Expensify/Expensify.cash/issues/123#:~:text=Please%20work/Expensify.cash ')).toEqual({ - href: 'https://github.com/Expensify/Expensify.cash/issues/123#:~:text=Please%20work/Expensify.cash ', - protocol: 'https://', - hostname: 'github.com', - path: '/Expensify/Expensify.cash/issues/123#:~:text=Please%20work/Expensify.cash ', - }); - expect(Url.getURLObject('https://github.com/Expensify/Expensify.cash/issues/123#:~:text=Please%20work/Expensify.cash ')).toEqual({ - href: 'https://github.com/Expensify/Expensify.cash/issues/123#:~:text=Please%20work/Expensify.cash ', - protocol: 'https://', - hostname: 'github.com', - path: '/Expensify/Expensify.cash/issues/123#:~:text=Please%20work/Expensify.cash ', - }); - expect(Url.getURLObject('mm..food ')).toEqual({ - href: undefined, - protocol: undefined, - hostname: undefined, - path: undefined, - }); - expect(Url.getURLObject('upwork.com/jobs/~016781e062ce860b84 ')).toEqual({ - href: 'upwork.com/jobs/~016781e062ce860b84 ', - protocol: undefined, - hostname: 'upwork.com', - path: '/jobs/~016781e062ce860b84 ', - }); - expect( - Url.getURLObject( - // eslint-disable-next-line max-len - "https://bastion1.sjc/logs/app/kibana#/discover?_g=()&_a=(columns:!(_source),index:'2125cbe0-28a9-11e9-a79c-3de0157ed580',interval:auto,query:(language:lucene,query:''),sort:!(timestamp,desc))", - ), - ).toEqual({ - // eslint-disable-next-line max-len - href: "https://bastion1.sjc/logs/app/kibana#/discover?_g=()&_a=(columns:!(_source),index:'2125cbe0-28a9-11e9-a79c-3de0157ed580',interval:auto,query:(language:lucene,query:''),sort:!(timestamp,desc))", - protocol: 'https://', - - hostname: 'bastion1.sjc', - // eslint-disable-next-line max-len - path: "/logs/app/kibana#/discover?_g=()&_a=(columns:!(_source),index:'2125cbe0-28a9-11e9-a79c-3de0157ed580',interval:auto,query:(language:lucene,query:''),sort:!(timestamp,desc))", - }); - expect(Url.getURLObject("google.com/maps/place/The+Flying'+Saucer/@42.4043314,-86.2742418,15z/data=!4m5!3m4!1s0x0:0xe28f6108670216bc!8m2!3d42.4043316!4d-86.2742121")).toEqual({ - href: "google.com/maps/place/The+Flying'+Saucer/@42.4043314,-86.2742418,15z/data=!4m5!3m4!1s0x0:0xe28f6108670216bc!8m2!3d42.4043316!4d-86.2742121", - protocol: undefined, - hostname: 'google.com', - path: "/maps/place/The+Flying'+Saucer/@42.4043314,-86.2742418,15z/data=!4m5!3m4!1s0x0:0xe28f6108670216bc!8m2!3d42.4043316!4d-86.2742121", - }); - expect( - Url.getURLObject( - // eslint-disable-next-line max-len - 'google.com/maps/place/%E9%9D%92%E5%B3%B6%E9%80%A3%E7%B5%A1%E8%88%B9%E4%B9%97%E5%A0%B4/@33.7363156,132.4877213,17.78z/data=!4m5!3m4!1s0x3545615c8c65bf7f:0xb89272c1a705a33f!8m2!3d33.7366776!4d132.4878843 ', - ), - ).toEqual({ - // eslint-disable-next-line max-len - href: 'google.com/maps/place/%E9%9D%92%E5%B3%B6%E9%80%A3%E7%B5%A1%E8%88%B9%E4%B9%97%E5%A0%B4/@33.7363156,132.4877213,17.78z/data=!4m5!3m4!1s0x3545615c8c65bf7f:0xb89272c1a705a33f!8m2!3d33.7366776!4d132.4878843 ', - protocol: undefined, - hostname: 'google.com', - // eslint-disable-next-line max-len - path: '/maps/place/%E9%9D%92%E5%B3%B6%E9%80%A3%E7%B5%A1%E8%88%B9%E4%B9%97%E5%A0%B4/@33.7363156,132.4877213,17.78z/data=!4m5!3m4!1s0x3545615c8c65bf7f:0xb89272c1a705a33f!8m2!3d33.7366776!4d132.4878843 ', - }); - expect( - Url.getURLObject( - // eslint-disable-next-line max-len - 'https://www.google.com/maps/place/Taj+Mahal+@is~"Awesome"/@27.1751496,78.0399535,17z/data=!4m12!1m6!3m5!1s0x39747121d702ff6d:0xdd2ae4803f767dde!2sTaj+Mahal!8m2!3d27.1751448!4d78.0421422!3m4!1s0x39747121d702ff6d:0xdd2ae4803f767dde!8m2!3d27.1751448!4d78.0421422', - ), - ).toEqual({ - // eslint-disable-next-line max-len - href: 'https://www.google.com/maps/place/Taj+Mahal+@is~"Awesome"/@27.1751496,78.0399535,17z/data=!4m12!1m6!3m5!1s0x39747121d702ff6d:0xdd2ae4803f767dde!2sTaj+Mahal!8m2!3d27.1751448!4d78.0421422!3m4!1s0x39747121d702ff6d:0xdd2ae4803f767dde!8m2!3d27.1751448!4d78.0421422', - protocol: 'https://', - hostname: 'www.google.com', - // eslint-disable-next-line max-len - path: '/maps/place/Taj+Mahal+@is~"Awesome"/@27.1751496,78.0399535,17z/data=!4m12!1m6!3m5!1s0x39747121d702ff6d:0xdd2ae4803f767dde!2sTaj+Mahal!8m2!3d27.1751448!4d78.0421422!3m4!1s0x39747121d702ff6d:0xdd2ae4803f767dde!8m2!3d27.1751448!4d78.0421422', - }); - }); - }); describe('hasSameExpensifyOrigin()', () => { describe('happy path', () => { it('It should work correctly', () => { @@ -224,8 +9,8 @@ describe('Url', () => { it('It should work correctly with www in both urls', () => { expect(Url.hasSameExpensifyOrigin('https://www.new.expensify.com/inbox/124', 'https://www.new.expensify.com/action/123')).toBe(true); }); - it('It should work correctly without https://', () => { - expect(Url.hasSameExpensifyOrigin('new.expensify.com/action/1234', 'new.expensify.com/action/123')).toBe(true); + it('It should work correctly with www in one of two urls', () => { + expect(Url.hasSameExpensifyOrigin('https://new.expensify.com/action/1234', 'https://www.new.expensify.com/action/123')).toBe(true); }); it('It should work correctly with old dot', () => { expect(Url.hasSameExpensifyOrigin('https://expensify.com/action/123', 'https://www.expensify.com/action/123')).toBe(true); @@ -244,9 +29,6 @@ describe('Url', () => { it('It should work correctly with www', () => { expect(Url.hasSameExpensifyOrigin('https://expensify.com/action/1234', 'https://www.new.expensify.com/action/123')).toBe(false); }); - it('It should work correctly with www in one of two urls', () => { - expect(Url.hasSameExpensifyOrigin('https://new.expensify.com/action/1234', 'https://www.new.expensify.com/action/123')).toBe(false); - }); }); }); }); From 1ae9ae488e7d4647f198d657715d4f8ce2194aae Mon Sep 17 00:00:00 2001 From: Talha Mahmood <36226639+Talha345@users.noreply.github.com> Date: Sat, 12 Aug 2023 14:20:53 +0200 Subject: [PATCH 2/4] Error handling added. --- src/libs/Url.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/libs/Url.js b/src/libs/Url.js index 7965a4043be7..0ac07b529bf9 100644 --- a/src/libs/Url.js +++ b/src/libs/Url.js @@ -16,8 +16,13 @@ function addTrailingForwardSlash(url) { * @returns {String} */ function getPathFromURL(url) { - const path = new URL(url).pathname; - return path.substring(1); // Remove the leading '/' + try { + const path = new URL(url).pathname; + return path.substring(1); // Remove the leading '/' + } catch (error) { + console.error('Error parsing URL:', error); + return ''; // Return empty string for invalid URLs + } } /** @@ -28,11 +33,16 @@ function getPathFromURL(url) { */ function hasSameExpensifyOrigin(url1, url2) { const removeW3 = (host) => host.replace(/^www\./i, ''); + try { + const parsedUrl1 = new URL(url1); + const parsedUrl2 = new URL(url2); - const parsedUrl1 = new URL(url1); - const parsedUrl2 = new URL(url2); - - return removeW3(parsedUrl1.host) === removeW3(parsedUrl2.host); + return removeW3(parsedUrl1.host) === removeW3(parsedUrl2.host); + } catch (error) { + // Handle invalid URLs or other parsing errors + console.error('Error parsing URLs:', error); + return false; + } } export { From b94b48f0db315391bd4d781a6ca0940e70f8ac29 Mon Sep 17 00:00:00 2001 From: Talha Mahmood <36226639+Talha345@users.noreply.github.com> Date: Sat, 12 Aug 2023 18:28:23 +0200 Subject: [PATCH 3/4] Move import to URL file --- __mocks__/react-native.js | 8 ++++++-- src/App.js | 1 - src/CONST.js | 10 ---------- src/libs/Url.js | 1 + 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/__mocks__/react-native.js b/__mocks__/react-native.js index 26a943ce62bc..006d1aee38af 100644 --- a/__mocks__/react-native.js +++ b/__mocks__/react-native.js @@ -1,7 +1,6 @@ // eslint-disable-next-line no-restricted-imports import * as ReactNative from 'react-native'; import _ from 'underscore'; -import CONST from '../src/CONST'; jest.doMock('react-native', () => { let url = 'https://new.expensify.com/'; @@ -15,7 +14,12 @@ jest.doMock('react-native', () => { // runs against index.native.js source and so anything that is testing a component reliant on withWindowDimensions() // would be most commonly assumed to be on a mobile phone vs. a tablet or desktop style view. This behavior can be // overridden by explicitly setting the dimensions inside a test via Dimensions.set() - let dimensions = CONST.TESTING.SCREEN_SIZE.SMALL; + let dimensions = { + width: 300, + height: 700, + scale: 1, + fontScale: 1, + }; return Object.setPrototypeOf( { diff --git a/src/App.js b/src/App.js index 278a72175f00..d8faa911f86b 100644 --- a/src/App.js +++ b/src/App.js @@ -22,7 +22,6 @@ import ThemeProvider from './styles/themes/ThemeProvider'; import ThemeStylesProvider from './styles/ThemeStylesProvider'; import {CurrentReportIDContextProvider} from './components/withCurrentReportID'; import {EnvironmentProvider} from './components/withEnvironment'; -import 'react-native-url-polyfill/auto'; import * as Session from './libs/actions/Session'; // For easier debugging and development, when we are in web we expose Onyx to the window, so you can more easily set data into Onyx diff --git a/src/CONST.js b/src/CONST.js index 4c19965837d9..605e311ef199 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1308,16 +1308,6 @@ const CONST = { INCORRECT_PASSWORD: 2, }, }, - TESTING: { - SCREEN_SIZE: { - SMALL: { - width: 300, - height: 700, - scale: 1, - fontScale: 1, - }, - }, - }, API_REQUEST_TYPE: { READ: 'read', WRITE: 'write', diff --git a/src/libs/Url.js b/src/libs/Url.js index 0ac07b529bf9..b64360a7babb 100644 --- a/src/libs/Url.js +++ b/src/libs/Url.js @@ -1,3 +1,4 @@ +import 'react-native-url-polyfill/auto'; /** * Add / to the end of any URL if not present * @param {String} url From 9df4895bcaecb44dbc1682ee2874d1e3efe1b8bf Mon Sep 17 00:00:00 2001 From: Talha Mahmood <36226639+Talha345@users.noreply.github.com> Date: Sun, 13 Aug 2023 20:43:41 +0200 Subject: [PATCH 4/4] Tests for getPathFromURL added. --- tests/unit/UrlTest.js | 59 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/unit/UrlTest.js b/tests/unit/UrlTest.js index 8ed674803f4f..7f92f09aeb37 100644 --- a/tests/unit/UrlTest.js +++ b/tests/unit/UrlTest.js @@ -1,6 +1,65 @@ const Url = require('../../src/libs/Url'); describe('Url', () => { + describe('getPathFromURL()', () => { + it('It should work correctly', () => { + expect(Url.getPathFromURL('http://www.foo.com')).toEqual(''); + expect(Url.getPathFromURL('http://foo.com/blah_blah')).toEqual('blah_blah'); + expect(Url.getPathFromURL('http://foo.com/blah_blah_(wikipedia)')).toEqual('blah_blah_(wikipedia)'); + expect(Url.getPathFromURL('http://www.example.com/wpstyle/?p=364')).toEqual('wpstyle/'); + expect(Url.getPathFromURL('https://www.example.com/foo/?bar=baz&inga=42&quux')).toEqual('foo/'); + expect(Url.getPathFromURL('http://foo.com/(something)?after=parens')).toEqual('(something)'); + expect(Url.getPathFromURL('http://code.google.com/events/#&product=browser')).toEqual('events/'); + expect(Url.getPathFromURL('http://foo.bar/?q=Test%20URL-encoded%20stuff')).toEqual(''); + expect(Url.getPathFromURL('http://www.test.com/path?param=123#123')).toEqual('path'); + expect(Url.getPathFromURL('http://1337.net')).toEqual(''); + expect(Url.getPathFromURL('http://a.b-c.de/')).toEqual(''); + expect(Url.getPathFromURL('https://sd1.sd2.docs.google.com/')).toEqual(''); + expect(Url.getPathFromURL('https://expensify.cash/#/r/1234')).toEqual(''); + expect(Url.getPathFromURL('https://github.com/Expensify/ReactNativeChat/pull/6.45')).toEqual('Expensify/ReactNativeChat/pull/6.45'); + expect(Url.getPathFromURL('https://github.com/Expensify/Expensify/issues/143,231')).toEqual('Expensify/Expensify/issues/143,231'); + expect(Url.getPathFromURL('testRareTLDs.beer')).toEqual(''); + expect(Url.getPathFromURL('test@expensify.com')).toEqual(''); + expect(Url.getPathFromURL('test.completelyFakeTLD')).toEqual(''); + expect( + Url.getPathFromURL( + // eslint-disable-next-line max-len + 'https://www.expensify.com/_devportal/tools/logSearch/#query=request_id:(%22Ufjjim%22)+AND+timestamp:[2021-01-08T03:48:10.389Z+TO+2021-01-08T05:48:10.389Z]&index=logs_expensify-008878)', + ), + ).toEqual('_devportal/tools/logSearch/'); + expect(Url.getPathFromURL('http://necolas.github.io/react-native-web/docs/?path=/docs/components-pressable--disabled ')).toEqual('react-native-web/docs/'); + expect(Url.getPathFromURL('https://github.com/Expensify/Expensify.cash/issues/123#:~:text=Please%20work/Expensify.cash ')).toEqual('Expensify/Expensify.cash/issues/123'); + expect(Url.getPathFromURL('https://github.com/Expensify/Expensify.cash/issues/123#:~:text=Please%20work/Expensify.cash ')).toEqual('Expensify/Expensify.cash/issues/123'); + expect(Url.getPathFromURL('mm..food ')).toEqual(''); + expect(Url.getPathFromURL('https://upwork.com/jobs/~016781e062ce860b84 ')).toEqual('jobs/~016781e062ce860b84'); + expect( + Url.getPathFromURL( + // eslint-disable-next-line max-len + "https://bastion1.sjc/logs/app/kibana#/discover?_g=()&_a=(columns:!(_source),index:'2125cbe0-28a9-11e9-a79c-3de0157ed580',interval:auto,query:(language:lucene,query:''),sort:!(timestamp,desc))", + ), + ).toEqual('logs/app/kibana'); + expect( + Url.getPathFromURL("https://google.com/maps/place/The+Flying'+Saucer/@42.4043314,-86.2742418,15z/data=!4m5!3m4!1s0x0:0xe28f6108670216bc!8m2!3d42.4043316!4d-86.2742121"), + ).toEqual("maps/place/The+Flying'+Saucer/@42.4043314,-86.2742418,15z/data=!4m5!3m4!1s0x0:0xe28f6108670216bc!8m2!3d42.4043316!4d-86.2742121"); + expect( + Url.getPathFromURL( + // eslint-disable-next-line max-len + 'https://google.com/maps/place/%E9%9D%92%E5%B3%B6%E9%80%A3%E7%B5%A1%E8%88%B9%E4%B9%97%E5%A0%B4/@33.7363156,132.4877213,17.78z/data=!4m5!3m4!1s0x3545615c8c65bf7f:0xb89272c1a705a33f!8m2!3d33.7366776!4d132.4878843 ', + ), + ).toEqual( + 'maps/place/%E9%9D%92%E5%B3%B6%E9%80%A3%E7%B5%A1%E8%88%B9%E4%B9%97%E5%A0%B4/@33.7363156,132.4877213,17.78z/data=!4m5!3m4!1s0x3545615c8c65bf7f:0xb89272c1a705a33f!8m2!3d33.7366776!4d132.4878843', + ); + expect( + Url.getPathFromURL( + // eslint-disable-next-line max-len + 'https://www.google.com/maps/place/Taj+Mahal+@is~"Awesome"/@27.1751496,78.0399535,17z/data=!4m12!1m6!3m5!1s0x39747121d702ff6d:0xdd2ae4803f767dde!2sTaj+Mahal!8m2!3d27.1751448!4d78.0421422!3m4!1s0x39747121d702ff6d:0xdd2ae4803f767dde!8m2!3d27.1751448!4d78.0421422', + ), + ).toEqual( + 'maps/place/Taj+Mahal+@is~%22Awesome%22/@27.1751496,78.0399535,17z/data=!4m12!1m6!3m5!1s0x39747121d702ff6d:0xdd2ae4803f767dde!2sTaj+Mahal!8m2!3d27.1751448!4d78.0421422!3m4!1s0x39747121d702ff6d:0xdd2ae4803f767dde!8m2!3d27.1751448!4d78.0421422', + ); + }); + }); + describe('hasSameExpensifyOrigin()', () => { describe('happy path', () => { it('It should work correctly', () => {