From 4f9aae6fc7a3b8454d7f44dfb6b7393a0c15573a Mon Sep 17 00:00:00 2001 From: Kane Thomas Date: Thu, 14 Nov 2024 18:21:49 -0500 Subject: [PATCH 1/4] intercept app store links and open link natively --- src/components/DappBrowser/BrowserTab.tsx | 10 +++++++-- src/components/DappBrowser/constants.ts | 5 +++++ src/components/DappBrowser/search/Search.tsx | 8 +++---- src/components/DappBrowser/utils.ts | 23 +++++++++++++++----- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/components/DappBrowser/BrowserTab.tsx b/src/components/DappBrowser/BrowserTab.tsx index 02cca1a6425..ac17646ff46 100644 --- a/src/components/DappBrowser/BrowserTab.tsx +++ b/src/components/DappBrowser/BrowserTab.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react'; import { Freeze } from 'react-freeze'; -import { StyleSheet } from 'react-native'; +import { Linking, StyleSheet } from 'react-native'; import { PanGestureHandler, PanGestureHandlerGestureEvent, @@ -52,7 +52,7 @@ import { useAnimatedTab } from './hooks/useAnimatedTab'; import { useTabScreenshotProvider } from './hooks/useTabScreenshotProvider'; import { freezeWebsite, getWebsiteMetadata, unfreezeWebsite } from './scripts'; import { BrowserTabProps, ScreenshotType } from './types'; -import { normalizeUrlForRecents } from './utils'; +import { isValidAppStoreUrl } from './utils'; export const BrowserTab = React.memo(function BrowserTab({ addRecent, setLogo, setTitle, tabId }: BrowserTabProps) { const { isDarkMode } = useColorMode(); @@ -309,6 +309,12 @@ const FreezableWebViewComponent = ({ (syntheticEvent: { nativeEvent: { targetUrl: string } }) => { const { nativeEvent } = syntheticEvent; const { targetUrl } = nativeEvent; + + if (isValidAppStoreUrl(targetUrl)) { + Linking.openURL(targetUrl); + return; + } + setParams({ url: targetUrl }); }, [setParams] diff --git a/src/components/DappBrowser/constants.ts b/src/components/DappBrowser/constants.ts index c3e29fd02cd..a38b1789c53 100644 --- a/src/components/DappBrowser/constants.ts +++ b/src/components/DappBrowser/constants.ts @@ -6,6 +6,11 @@ export const HTTP = 'http://'; export const HTTPS = 'https://'; export const RAINBOW_HOME = 'RAINBOW_HOME'; +export const IOS_APP_STORE_URL_PREFIXES = ['itms-apps://', 'itms-appss://', 'https://itunes.apple.com', 'https://apps.apple.com']; +export const ANDROID_APP_STORE_URL_PREFIXES = ['market://', 'https://play.google.com/store', 'https://play.google.com/store/apps']; + +export const APP_STORE_URL_PREFIXES = [...IOS_APP_STORE_URL_PREFIXES, ...ANDROID_APP_STORE_URL_PREFIXES]; + export const USER_AGENT = { IOS: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1', ANDROID: 'Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.103 Mobile Safari/537.36', diff --git a/src/components/DappBrowser/search/Search.tsx b/src/components/DappBrowser/search/Search.tsx index df2ef1470db..45147cdcd81 100644 --- a/src/components/DappBrowser/search/Search.tsx +++ b/src/components/DappBrowser/search/Search.tsx @@ -7,11 +7,11 @@ import React, { useCallback } from 'react'; import { StyleSheet, View } from 'react-native'; import Animated, { SharedValue, runOnJS, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; import { useBrowserContext } from '../BrowserContext'; -import { GOOGLE_SEARCH_URL, HTTP, HTTPS } from '../constants'; +import { GOOGLE_SEARCH_URL, HTTPS } from '../constants'; import { AccountIcon } from '../search-input/AccountIcon'; import { SearchInput } from '../search-input/SearchInput'; import { TabButton } from '../search-input/TabButton'; -import { isValidURL, isValidURLWorklet } from '../utils'; +import { isMissingValidProtocol, isMissingValidProtocolWorklet, isValidURL, isValidURLWorklet } from '../utils'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { useBrowserWorkletsContext } from '../BrowserWorkletsContext'; import { SearchResults } from './results/SearchResults'; @@ -72,7 +72,7 @@ export const Search = () => { if (!isValidURL(newUrl)) { newUrl = GOOGLE_SEARCH_URL + newUrl; - } else if (!newUrl.startsWith(HTTP) && !newUrl.startsWith(HTTPS)) { + } else if (isMissingValidProtocol(newUrl)) { newUrl = HTTPS + newUrl; } @@ -94,7 +94,7 @@ export const Search = () => { if (!isValidURLWorklet(newUrl)) { newUrl = GOOGLE_SEARCH_URL + newUrl; - } else if (!newUrl.startsWith(HTTP) && !newUrl.startsWith(HTTPS)) { + } else if (isMissingValidProtocolWorklet(newUrl)) { newUrl = HTTPS + newUrl; } diff --git a/src/components/DappBrowser/utils.ts b/src/components/DappBrowser/utils.ts index 7d1d885311b..25d465715a7 100644 --- a/src/components/DappBrowser/utils.ts +++ b/src/components/DappBrowser/utils.ts @@ -1,7 +1,7 @@ import { Share } from 'react-native'; import { WebViewNavigationEvent } from 'react-native-webview/lib/RNCWebViewNativeComponent'; import { RainbowError, logger } from '@/logger'; -import { HTTP, HTTPS, RAINBOW_HOME } from './constants'; +import { HTTP, HTTPS, RAINBOW_HOME, APP_STORE_URL_PREFIXES } from './constants'; // ---------------------------------------------------------------------------- // // URL validation regex breakdown here: https://mathiasbynens.be/demo/url-regex @@ -12,9 +12,22 @@ import { HTTP, HTTPS, RAINBOW_HOME } from './constants'; const URL_PATTERN_REGEX = /^(?:(?:(?:https?):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i; +export function isMissingValidProtocol(url: string): boolean { + return !url.startsWith(HTTP) && !url.startsWith(HTTPS); +} + +export function isMissingValidProtocolWorklet(url: string): boolean { + 'worklet'; + return !url.startsWith(HTTP) && !url.startsWith(HTTPS); +} + +export function isValidAppStoreUrl(url: string): boolean { + return APP_STORE_URL_PREFIXES.some(prefix => url.startsWith(prefix)); +} + export function isValidURL(url: string): boolean { let urlForValidation = url.trim(); - if (!urlForValidation.startsWith(HTTP) && !urlForValidation.startsWith(HTTPS)) { + if (isMissingValidProtocol(urlForValidation)) { urlForValidation = HTTPS + urlForValidation; } return URL_PATTERN_REGEX.test(urlForValidation); @@ -23,7 +36,7 @@ export function isValidURL(url: string): boolean { export function isValidURLWorklet(url: string): boolean { 'worklet'; let urlForValidation = url.trim(); - if (!urlForValidation.startsWith(HTTP) && !urlForValidation.startsWith(HTTPS)) { + if (isMissingValidProtocolWorklet(urlForValidation)) { urlForValidation = HTTPS + urlForValidation; } return URL_PATTERN_REGEX.test(urlForValidation); @@ -34,7 +47,7 @@ export const normalizeUrl = (url: string): string => { return ''; } let normalizedUrl = url; - if (!normalizedUrl.startsWith(HTTP) && !normalizedUrl.startsWith(HTTPS)) { + if (isMissingValidProtocol(normalizedUrl)) { normalizedUrl = HTTPS + normalizedUrl; } if (!normalizedUrl.endsWith('/') && !normalizedUrl.includes('?')) { @@ -49,7 +62,7 @@ export const normalizeUrlWorklet = (url: string): string => { return ''; } let normalizedUrl = url; - if (!normalizedUrl.startsWith(HTTP) && !normalizedUrl.startsWith(HTTPS)) { + if (isMissingValidProtocolWorklet(normalizedUrl)) { normalizedUrl = HTTPS + normalizedUrl; } if (!normalizedUrl.endsWith('/') && !normalizedUrl.includes('?')) { From a9ca60d4d1853c53ab359ebaeeb5e7a1574f5cba Mon Sep 17 00:00:00 2001 From: Kane Thomas Date: Fri, 15 Nov 2024 10:52:08 -0500 Subject: [PATCH 2/4] remove redundant android store url --- src/components/DappBrowser/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DappBrowser/constants.ts b/src/components/DappBrowser/constants.ts index a38b1789c53..907f0faf194 100644 --- a/src/components/DappBrowser/constants.ts +++ b/src/components/DappBrowser/constants.ts @@ -7,7 +7,7 @@ export const HTTPS = 'https://'; export const RAINBOW_HOME = 'RAINBOW_HOME'; export const IOS_APP_STORE_URL_PREFIXES = ['itms-apps://', 'itms-appss://', 'https://itunes.apple.com', 'https://apps.apple.com']; -export const ANDROID_APP_STORE_URL_PREFIXES = ['market://', 'https://play.google.com/store', 'https://play.google.com/store/apps']; +export const ANDROID_APP_STORE_URL_PREFIXES = ['market://', 'https://play.google.com/store']; export const APP_STORE_URL_PREFIXES = [...IOS_APP_STORE_URL_PREFIXES, ...ANDROID_APP_STORE_URL_PREFIXES]; From d93dcadac02e577493eaf66c591564a3e83317d3 Mon Sep 17 00:00:00 2001 From: Kane Thomas Date: Mon, 18 Nov 2024 11:16:01 -0500 Subject: [PATCH 3/4] remove duplicate non worklet functions --- src/components/DappBrowser/search/Search.tsx | 7 ++--- src/components/DappBrowser/utils.ts | 28 -------------------- src/state/browser/browserStore.ts | 6 ++--- 3 files changed, 7 insertions(+), 34 deletions(-) diff --git a/src/components/DappBrowser/search/Search.tsx b/src/components/DappBrowser/search/Search.tsx index 45147cdcd81..5948d34f900 100644 --- a/src/components/DappBrowser/search/Search.tsx +++ b/src/components/DappBrowser/search/Search.tsx @@ -11,7 +11,7 @@ import { GOOGLE_SEARCH_URL, HTTPS } from '../constants'; import { AccountIcon } from '../search-input/AccountIcon'; import { SearchInput } from '../search-input/SearchInput'; import { TabButton } from '../search-input/TabButton'; -import { isMissingValidProtocol, isMissingValidProtocolWorklet, isValidURL, isValidURLWorklet } from '../utils'; +import { isMissingValidProtocolWorklet, isValidURLWorklet } from '../utils'; import { DEVICE_WIDTH } from '@/utils/deviceUtils'; import { useBrowserWorkletsContext } from '../BrowserWorkletsContext'; import { SearchResults } from './results/SearchResults'; @@ -70,9 +70,9 @@ export const Search = () => { (updatedUrl: string) => { let newUrl = updatedUrl; - if (!isValidURL(newUrl)) { + if (!isValidURLWorklet(newUrl)) { newUrl = GOOGLE_SEARCH_URL + newUrl; - } else if (isMissingValidProtocol(newUrl)) { + } else if (isMissingValidProtocolWorklet(newUrl)) { newUrl = HTTPS + newUrl; } @@ -88,6 +88,7 @@ export const Search = () => { 'worklet'; let newUrl = updatedUrl; + console.log('url submit'); if (searchResults.value.length > 0 && searchResults.value[0].url) { newUrl = searchResults.value[0].url; } diff --git a/src/components/DappBrowser/utils.ts b/src/components/DappBrowser/utils.ts index 25d465715a7..93ce83b5fe3 100644 --- a/src/components/DappBrowser/utils.ts +++ b/src/components/DappBrowser/utils.ts @@ -25,14 +25,6 @@ export function isValidAppStoreUrl(url: string): boolean { return APP_STORE_URL_PREFIXES.some(prefix => url.startsWith(prefix)); } -export function isValidURL(url: string): boolean { - let urlForValidation = url.trim(); - if (isMissingValidProtocol(urlForValidation)) { - urlForValidation = HTTPS + urlForValidation; - } - return URL_PATTERN_REGEX.test(urlForValidation); -} - export function isValidURLWorklet(url: string): boolean { 'worklet'; let urlForValidation = url.trim(); @@ -42,20 +34,6 @@ export function isValidURLWorklet(url: string): boolean { return URL_PATTERN_REGEX.test(urlForValidation); } -export const normalizeUrl = (url: string): string => { - if (!url) { - return ''; - } - let normalizedUrl = url; - if (isMissingValidProtocol(normalizedUrl)) { - normalizedUrl = HTTPS + normalizedUrl; - } - if (!normalizedUrl.endsWith('/') && !normalizedUrl.includes('?')) { - normalizedUrl += '/'; - } - return normalizedUrl; -}; - export const normalizeUrlWorklet = (url: string): string => { 'worklet'; if (!url) { @@ -101,12 +79,6 @@ export const formatUrl = (url: string, formatSearches = true, prettifyUrl = true return formattedValue; }; -export const generateUniqueId = (): string => { - const timestamp = Date.now().toString(36); - const randomString = Math.random().toString(36).slice(2, 7); - return `${timestamp}${randomString}`; -}; - export function generateUniqueIdWorklet(): string { 'worklet'; const timestamp = Date.now().toString(36); diff --git a/src/state/browser/browserStore.ts b/src/state/browser/browserStore.ts index ee3bf8f7be3..9133add6988 100644 --- a/src/state/browser/browserStore.ts +++ b/src/state/browser/browserStore.ts @@ -4,7 +4,7 @@ import { create } from 'zustand'; import { PersistStorage, StorageValue, persist, subscribeWithSelector } from 'zustand/middleware'; import { RAINBOW_HOME } from '@/components/DappBrowser/constants'; import { TabData, TabId } from '@/components/DappBrowser/types'; -import { generateUniqueId, normalizeUrl } from '@/components/DappBrowser/utils'; +import { generateUniqueIdWorklet, normalizeUrlWorklet } from '@/components/DappBrowser/utils'; import { RainbowError, logger } from '@/logger'; const BROWSER_STORAGE_ID = 'browserStore'; @@ -104,7 +104,7 @@ const persistedBrowserStorage: PersistStorage = { }; const INITIAL_ACTIVE_TAB_INDEX = 0; -const INITIAL_TAB_IDS = [generateUniqueId()]; +const INITIAL_TAB_IDS = [generateUniqueIdWorklet()]; const INITIAL_TABS_DATA = new Map([[INITIAL_TAB_IDS[0], { url: RAINBOW_HOME }]]); const INITIAL_PERSISTED_TAB_URLS: Record = { [INITIAL_TAB_IDS[0]]: RAINBOW_HOME }; @@ -154,7 +154,7 @@ export const useBrowserStore = create()( const existingTabData = state.tabsData.get(tabIdToUse); if (existingTabData?.url !== url) { const newTabsData = new Map(state.tabsData); - newTabsData.set(tabIdToUse, { ...existingTabData, url: normalizeUrl(url) }); + newTabsData.set(tabIdToUse, { ...existingTabData, url: normalizeUrlWorklet(url) }); return { tabsData: newTabsData }; } return state; From a091f7fadb2ef72dde92d82953193267150476f7 Mon Sep 17 00:00:00 2001 From: Kane Thomas Date: Mon, 18 Nov 2024 15:08:34 -0500 Subject: [PATCH 4/4] remove console log --- src/components/DappBrowser/search/Search.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/DappBrowser/search/Search.tsx b/src/components/DappBrowser/search/Search.tsx index 5948d34f900..d8f2983f1fc 100644 --- a/src/components/DappBrowser/search/Search.tsx +++ b/src/components/DappBrowser/search/Search.tsx @@ -88,7 +88,6 @@ export const Search = () => { 'worklet'; let newUrl = updatedUrl; - console.log('url submit'); if (searchResults.value.length > 0 && searchResults.value[0].url) { newUrl = searchResults.value[0].url; }