diff --git a/package.json b/package.json index 837a8d0e..4a3a2a7d 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,6 @@ "js-sha256": "^0.9.0", "jwt-decode": "^4.0.0", "lande": "^1.0.10", - "libphonenumber-js": "^1.10.53", "lodash.chunk": "^4.2.0", "lodash.debounce": "^4.0.8", "lodash.isequal": "^4.5.0", @@ -137,7 +136,7 @@ "mobx": "^6.6.1", "mobx-react-lite": "^3.4.0", "mobx-utils": "^6.0.6", - "nanoid": "^5.0.2", + "nanoid": "^5.0.5", "normalize-url": "^8.0.0", "patch-package": "^6.5.1", "postinstall-postinstall": "^2.1.0", @@ -164,6 +163,7 @@ "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", "react-native-svg": "14.1.0", + "react-native-ui-text-view": "link:./modules/react-native-ui-text-view", "react-native-url-polyfill": "^1.3.0", "react-native-uuid": "^2.0.1", "react-native-version-number": "^0.3.6", @@ -178,8 +178,7 @@ "tlds": "^1.234.0", "use-deep-compare": "^1.1.0", "zeego": "^1.6.2", - "zod": "^3.20.2", - "react-native-ui-text-view": "link:./modules/react-native-ui-text-view" + "zod": "^3.20.2" }, "devDependencies": { "@atproto/dev-env": "^0.2.28", diff --git a/src/lib/country-codes.ts b/src/lib/country-codes.ts deleted file mode 100644 index 9c9da84c..00000000 --- a/src/lib/country-codes.ts +++ /dev/null @@ -1,256 +0,0 @@ -import {CountryCode} from 'libphonenumber-js' - -// ISO 3166-1 alpha-2 codes - -export interface CountryCodeMap { - code2: CountryCode - name: string -} - -export const COUNTRY_CODES: CountryCodeMap[] = [ - {code2: 'AF', name: 'Afghanistan (+93)'}, - {code2: 'AX', name: 'Åland Islands (+358)'}, - {code2: 'AL', name: 'Albania (+355)'}, - {code2: 'DZ', name: 'Algeria (+213)'}, - {code2: 'AS', name: 'American Samoa (+1)'}, - {code2: 'AD', name: 'Andorra (+376)'}, - {code2: 'AO', name: 'Angola (+244)'}, - {code2: 'AI', name: 'Anguilla (+1)'}, - {code2: 'AG', name: 'Antigua and Barbuda (+1)'}, - {code2: 'AR', name: 'Argentina (+54)'}, - {code2: 'AM', name: 'Armenia (+374)'}, - {code2: 'AW', name: 'Aruba (+297)'}, - {code2: 'AU', name: 'Australia (+61)'}, - {code2: 'AT', name: 'Austria (+43)'}, - {code2: 'AZ', name: 'Azerbaijan (+994)'}, - {code2: 'BS', name: 'Bahamas (+1)'}, - {code2: 'BH', name: 'Bahrain (+973)'}, - {code2: 'BD', name: 'Bangladesh (+880)'}, - {code2: 'BB', name: 'Barbados (+1)'}, - {code2: 'BY', name: 'Belarus (+375)'}, - {code2: 'BE', name: 'Belgium (+32)'}, - {code2: 'BZ', name: 'Belize (+501)'}, - {code2: 'BJ', name: 'Benin (+229)'}, - {code2: 'BM', name: 'Bermuda (+1)'}, - {code2: 'BT', name: 'Bhutan (+975)'}, - {code2: 'BO', name: 'Bolivia (Plurinational State of) (+591)'}, - {code2: 'BQ', name: 'Bonaire, Sint Eustatius and Saba (+599)'}, - {code2: 'BA', name: 'Bosnia and Herzegovina (+387)'}, - {code2: 'BW', name: 'Botswana (+267)'}, - {code2: 'BR', name: 'Brazil (+55)'}, - {code2: 'IO', name: 'British Indian Ocean Territory (+246)'}, - {code2: 'BN', name: 'Brunei Darussalam (+673)'}, - {code2: 'BG', name: 'Bulgaria (+359)'}, - {code2: 'BF', name: 'Burkina Faso (+226)'}, - {code2: 'BI', name: 'Burundi (+257)'}, - {code2: 'CV', name: 'Cabo Verde (+238)'}, - {code2: 'KH', name: 'Cambodia (+855)'}, - {code2: 'CM', name: 'Cameroon (+237)'}, - {code2: 'CA', name: 'Canada (+1)'}, - {code2: 'KY', name: 'Cayman Islands (+1)'}, - {code2: 'CF', name: 'Central African Republic (+236)'}, - {code2: 'TD', name: 'Chad (+235)'}, - {code2: 'CL', name: 'Chile (+56)'}, - {code2: 'CN', name: 'China (+86)'}, - {code2: 'CX', name: 'Christmas Island (+61)'}, - {code2: 'CC', name: 'Cocos (Keeling) Islands (+61)'}, - {code2: 'CO', name: 'Colombia (+57)'}, - {code2: 'KM', name: 'Comoros (+269)'}, - {code2: 'CG', name: 'Congo (+242)'}, - {code2: 'CD', name: 'Congo, Democratic Republic of the (+243)'}, - {code2: 'CK', name: 'Cook Islands (+682)'}, - {code2: 'CR', name: 'Costa Rica (+506)'}, - {code2: 'CI', name: "Côte d'Ivoire (+225)"}, - {code2: 'HR', name: 'Croatia (+385)'}, - {code2: 'CU', name: 'Cuba (+53)'}, - {code2: 'CW', name: 'Curaçao (+599)'}, - {code2: 'CY', name: 'Cyprus (+357)'}, - {code2: 'CZ', name: 'Czechia (+420)'}, - {code2: 'DK', name: 'Denmark (+45)'}, - {code2: 'DJ', name: 'Djibouti (+253)'}, - {code2: 'DM', name: 'Dominica (+1)'}, - {code2: 'DO', name: 'Dominican Republic (+1)'}, - {code2: 'EC', name: 'Ecuador (+593)'}, - {code2: 'EG', name: 'Egypt (+20)'}, - {code2: 'SV', name: 'El Salvador (+503)'}, - {code2: 'GQ', name: 'Equatorial Guinea (+240)'}, - {code2: 'ER', name: 'Eritrea (+291)'}, - {code2: 'EE', name: 'Estonia (+372)'}, - {code2: 'SZ', name: 'Eswatini (+268)'}, - {code2: 'ET', name: 'Ethiopia (+251)'}, - {code2: 'FK', name: 'Falkland Islands (Malvinas) (+500)'}, - {code2: 'FO', name: 'Faroe Islands (+298)'}, - {code2: 'FJ', name: 'Fiji (+679)'}, - {code2: 'FI', name: 'Finland (+358)'}, - {code2: 'FR', name: 'France (+33)'}, - {code2: 'GF', name: 'French Guiana (+594)'}, - {code2: 'PF', name: 'French Polynesia (+689)'}, - {code2: 'GA', name: 'Gabon (+241)'}, - {code2: 'GM', name: 'Gambia (+220)'}, - {code2: 'GE', name: 'Georgia (+995)'}, - {code2: 'DE', name: 'Germany (+49)'}, - {code2: 'GH', name: 'Ghana (+233)'}, - {code2: 'GI', name: 'Gibraltar (+350)'}, - {code2: 'GR', name: 'Greece (+30)'}, - {code2: 'GL', name: 'Greenland (+299)'}, - {code2: 'GD', name: 'Grenada (+1)'}, - {code2: 'GP', name: 'Guadeloupe (+590)'}, - {code2: 'GU', name: 'Guam (+1)'}, - {code2: 'GT', name: 'Guatemala (+502)'}, - {code2: 'GG', name: 'Guernsey (+44)'}, - {code2: 'GN', name: 'Guinea (+224)'}, - {code2: 'GW', name: 'Guinea-Bissau (+245)'}, - {code2: 'GY', name: 'Guyana (+592)'}, - {code2: 'HT', name: 'Haiti (+509)'}, - {code2: 'VA', name: 'Holy See (+39)'}, - {code2: 'HN', name: 'Honduras (+504)'}, - {code2: 'HK', name: 'Hong Kong (+852)'}, - {code2: 'HU', name: 'Hungary (+36)'}, - {code2: 'IS', name: 'Iceland (+354)'}, - {code2: 'IN', name: 'India (+91)'}, - {code2: 'ID', name: 'Indonesia (+62)'}, - {code2: 'IR', name: 'Iran (Islamic Republic of) (+98)'}, - {code2: 'IQ', name: 'Iraq (+964)'}, - {code2: 'IE', name: 'Ireland (+353)'}, - {code2: 'IM', name: 'Isle of Man (+44)'}, - {code2: 'IL', name: 'Israel (+972)'}, - {code2: 'IT', name: 'Italy (+39)'}, - {code2: 'JM', name: 'Jamaica (+1)'}, - {code2: 'JP', name: 'Japan (+81)'}, - {code2: 'JE', name: 'Jersey (+44)'}, - {code2: 'JO', name: 'Jordan (+962)'}, - {code2: 'KZ', name: 'Kazakhstan (+7)'}, - {code2: 'KE', name: 'Kenya (+254)'}, - {code2: 'KI', name: 'Kiribati (+686)'}, - {code2: 'KP', name: "Korea (Democratic People's Republic of) (+850)"}, - {code2: 'KR', name: 'Korea, Republic of (+82)'}, - {code2: 'KW', name: 'Kuwait (+965)'}, - {code2: 'KG', name: 'Kyrgyzstan (+996)'}, - {code2: 'LA', name: "Lao People's Democratic Republic (+856)"}, - {code2: 'LV', name: 'Latvia (+371)'}, - {code2: 'LB', name: 'Lebanon (+961)'}, - {code2: 'LS', name: 'Lesotho (+266)'}, - {code2: 'LR', name: 'Liberia (+231)'}, - {code2: 'LY', name: 'Libya (+218)'}, - {code2: 'LI', name: 'Liechtenstein (+423)'}, - {code2: 'LT', name: 'Lithuania (+370)'}, - {code2: 'LU', name: 'Luxembourg (+352)'}, - {code2: 'MO', name: 'Macao (+853)'}, - {code2: 'MG', name: 'Madagascar (+261)'}, - {code2: 'MW', name: 'Malawi (+265)'}, - {code2: 'MY', name: 'Malaysia (+60)'}, - {code2: 'MV', name: 'Maldives (+960)'}, - {code2: 'ML', name: 'Mali (+223)'}, - {code2: 'MT', name: 'Malta (+356)'}, - {code2: 'MH', name: 'Marshall Islands (+692)'}, - {code2: 'MQ', name: 'Martinique (+596)'}, - {code2: 'MR', name: 'Mauritania (+222)'}, - {code2: 'MU', name: 'Mauritius (+230)'}, - {code2: 'YT', name: 'Mayotte (+262)'}, - {code2: 'MX', name: 'Mexico (+52)'}, - {code2: 'FM', name: 'Micronesia (Federated States of) (+691)'}, - {code2: 'MD', name: 'Moldova, Republic of (+373)'}, - {code2: 'MC', name: 'Monaco (+377)'}, - {code2: 'MN', name: 'Mongolia (+976)'}, - {code2: 'ME', name: 'Montenegro (+382)'}, - {code2: 'MS', name: 'Montserrat (+1)'}, - {code2: 'MA', name: 'Morocco (+212)'}, - {code2: 'MZ', name: 'Mozambique (+258)'}, - {code2: 'MM', name: 'Myanmar (+95)'}, - {code2: 'NA', name: 'Namibia (+264)'}, - {code2: 'NR', name: 'Nauru (+674)'}, - {code2: 'NP', name: 'Nepal (+977)'}, - {code2: 'NL', name: 'Netherlands, Kingdom of the (+31)'}, - {code2: 'NC', name: 'New Caledonia (+687)'}, - {code2: 'NZ', name: 'New Zealand (+64)'}, - {code2: 'NI', name: 'Nicaragua (+505)'}, - {code2: 'NE', name: 'Niger (+227)'}, - {code2: 'NG', name: 'Nigeria (+234)'}, - {code2: 'NU', name: 'Niue (+683)'}, - {code2: 'NF', name: 'Norfolk Island (+672)'}, - {code2: 'MK', name: 'North Macedonia (+389)'}, - {code2: 'MP', name: 'Northern Mariana Islands (+1)'}, - {code2: 'NO', name: 'Norway (+47)'}, - {code2: 'OM', name: 'Oman (+968)'}, - {code2: 'PK', name: 'Pakistan (+92)'}, - {code2: 'PW', name: 'Palau (+680)'}, - {code2: 'PS', name: 'Palestine, State of (+970)'}, - {code2: 'PA', name: 'Panama (+507)'}, - {code2: 'PG', name: 'Papua New Guinea (+675)'}, - {code2: 'PY', name: 'Paraguay (+595)'}, - {code2: 'PE', name: 'Peru (+51)'}, - {code2: 'PH', name: 'Philippines (+63)'}, - {code2: 'PL', name: 'Poland (+48)'}, - {code2: 'PT', name: 'Portugal (+351)'}, - {code2: 'PR', name: 'Puerto Rico (+1)'}, - {code2: 'QA', name: 'Qatar (+974)'}, - {code2: 'RE', name: 'Réunion (+262)'}, - {code2: 'RO', name: 'Romania (+40)'}, - {code2: 'RU', name: 'Russian Federation (+7)'}, - {code2: 'RW', name: 'Rwanda (+250)'}, - {code2: 'BL', name: 'Saint Barthélemy (+590)'}, - {code2: 'SH', name: 'Saint Helena, Ascension and Tristan da Cunha (+290)'}, - {code2: 'KN', name: 'Saint Kitts and Nevis (+1)'}, - {code2: 'LC', name: 'Saint Lucia (+1)'}, - {code2: 'MF', name: 'Saint Martin (French part) (+590)'}, - {code2: 'PM', name: 'Saint Pierre and Miquelon (+508)'}, - {code2: 'VC', name: 'Saint Vincent and the Grenadines (+1)'}, - {code2: 'WS', name: 'Samoa (+685)'}, - {code2: 'SM', name: 'San Marino (+378)'}, - {code2: 'ST', name: 'Sao Tome and Principe (+239)'}, - {code2: 'SA', name: 'Saudi Arabia (+966)'}, - {code2: 'SN', name: 'Senegal (+221)'}, - {code2: 'RS', name: 'Serbia (+381)'}, - {code2: 'SC', name: 'Seychelles (+248)'}, - {code2: 'SL', name: 'Sierra Leone (+232)'}, - {code2: 'SG', name: 'Singapore (+65)'}, - {code2: 'SX', name: 'Sint Maarten (Dutch part) (+1)'}, - {code2: 'SK', name: 'Slovakia (+421)'}, - {code2: 'SI', name: 'Slovenia (+386)'}, - {code2: 'SB', name: 'Solomon Islands (+677)'}, - {code2: 'SO', name: 'Somalia (+252)'}, - {code2: 'ZA', name: 'South Africa (+27)'}, - {code2: 'SS', name: 'South Sudan (+211)'}, - {code2: 'ES', name: 'Spain (+34)'}, - {code2: 'LK', name: 'Sri Lanka (+94)'}, - {code2: 'SD', name: 'Sudan (+249)'}, - {code2: 'SR', name: 'Suriname (+597)'}, - {code2: 'SJ', name: 'Svalbard and Jan Mayen (+47)'}, - {code2: 'SE', name: 'Sweden (+46)'}, - {code2: 'CH', name: 'Switzerland (+41)'}, - {code2: 'SY', name: 'Syrian Arab Republic (+963)'}, - {code2: 'TW', name: 'Taiwan (+886)'}, - {code2: 'TJ', name: 'Tajikistan (+992)'}, - {code2: 'TZ', name: 'Tanzania, United Republic of (+255)'}, - {code2: 'TH', name: 'Thailand (+66)'}, - {code2: 'TL', name: 'Timor-Leste (+670)'}, - {code2: 'TG', name: 'Togo (+228)'}, - {code2: 'TK', name: 'Tokelau (+690)'}, - {code2: 'TO', name: 'Tonga (+676)'}, - {code2: 'TT', name: 'Trinidad and Tobago (+1)'}, - {code2: 'TN', name: 'Tunisia (+216)'}, - {code2: 'TR', name: 'Türkiye (+90)'}, - {code2: 'TM', name: 'Turkmenistan (+993)'}, - {code2: 'TC', name: 'Turks and Caicos Islands (+1)'}, - {code2: 'TV', name: 'Tuvalu (+688)'}, - {code2: 'UG', name: 'Uganda (+256)'}, - {code2: 'UA', name: 'Ukraine (+380)'}, - {code2: 'AE', name: 'United Arab Emirates (+971)'}, - { - code2: 'GB', - name: 'United Kingdom of Great Britain and Northern Ireland (+44)', - }, - {code2: 'US', name: 'United States of America (+1)'}, - {code2: 'UY', name: 'Uruguay (+598)'}, - {code2: 'UZ', name: 'Uzbekistan (+998)'}, - {code2: 'VU', name: 'Vanuatu (+678)'}, - {code2: 'VE', name: 'Venezuela (Bolivarian Republic of) (+58)'}, - {code2: 'VN', name: 'Viet Nam (+84)'}, - {code2: 'VG', name: 'Virgin Islands (British) (+1)'}, - {code2: 'VI', name: 'Virgin Islands (U.S.) (+1)'}, - {code2: 'WF', name: 'Wallis and Futuna (+681)'}, - {code2: 'EH', name: 'Western Sahara (+212)'}, - {code2: 'YE', name: 'Yemen (+967)'}, - {code2: 'ZM', name: 'Zambia (+260)'}, - {code2: 'ZW', name: 'Zimbabwe (+263)'}, -] diff --git a/src/view/com/auth/create/CaptchaWebView.tsx b/src/view/com/auth/create/CaptchaWebView.tsx new file mode 100644 index 00000000..b0de8b4a --- /dev/null +++ b/src/view/com/auth/create/CaptchaWebView.tsx @@ -0,0 +1,86 @@ +import React from 'react' +import {WebView, WebViewNavigation} from 'react-native-webview' +import {ShouldStartLoadRequest} from 'react-native-webview/lib/WebViewTypes' +import {StyleSheet} from 'react-native' +import {CreateAccountState} from 'view/com/auth/create/state' + +const ALLOWED_HOSTS = [ + 'bsky.social', + 'bsky.app', + 'staging.bsky.app', + 'staging.bsky.dev', + 'js.hcaptcha.com', + 'newassets.hcaptcha.com', + 'api2.hcaptcha.com', +] + +export function CaptchaWebView({ + url, + stateParam, + uiState, + onSuccess, + onError, +}: { + url: string + stateParam: string + uiState?: CreateAccountState + onSuccess: (code: string) => void + onError: () => void +}) { + const redirectHost = React.useMemo(() => { + if (!uiState?.serviceUrl) return 'bsky.app' + + return uiState?.serviceUrl && + new URL(uiState?.serviceUrl).host === 'staging.bsky.dev' + ? 'staging.bsky.app' + : 'bsky.app' + }, [uiState?.serviceUrl]) + + const wasSuccessful = React.useRef(false) + + const onShouldStartLoadWithRequest = React.useCallback( + (event: ShouldStartLoadRequest) => { + const urlp = new URL(event.url) + return ALLOWED_HOSTS.includes(urlp.host) + }, + [], + ) + + const onNavigationStateChange = React.useCallback( + (e: WebViewNavigation) => { + if (wasSuccessful.current) return + + const urlp = new URL(e.url) + if (urlp.host !== redirectHost) return + + const code = urlp.searchParams.get('code') + if (urlp.searchParams.get('state') !== stateParam || !code) { + onError() + return + } + + wasSuccessful.current = true + onSuccess(code) + }, + [redirectHost, stateParam, onSuccess, onError], + ) + + return ( + + ) +} + +const styles = StyleSheet.create({ + webview: { + flex: 1, + backgroundColor: 'transparent', + borderRadius: 10, + }, +}) diff --git a/src/view/com/auth/create/CaptchaWebView.web.tsx b/src/view/com/auth/create/CaptchaWebView.web.tsx new file mode 100644 index 00000000..7791a58d --- /dev/null +++ b/src/view/com/auth/create/CaptchaWebView.web.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import {StyleSheet} from 'react-native' + +// @ts-ignore web only, we will always redirect to the app on web (CORS) +const REDIRECT_HOST = new URL(window.location.href).host + +export function CaptchaWebView({ + url, + stateParam, + onSuccess, + onError, +}: { + url: string + stateParam: string + onSuccess: (code: string) => void + onError: () => void +}) { + const onLoad = React.useCallback(() => { + // @ts-ignore web + const frame: HTMLIFrameElement = document.getElementById( + 'captcha-iframe', + ) as HTMLIFrameElement + + try { + // @ts-ignore web + const href = frame?.contentWindow?.location.href + if (!href) return + const urlp = new URL(href) + + // This shouldn't happen with CORS protections, but for good measure + if (urlp.host !== REDIRECT_HOST) return + + const code = urlp.searchParams.get('code') + if (urlp.searchParams.get('state') !== stateParam || !code) { + onError() + return + } + onSuccess(code) + } catch (e) { + // We don't need to handle this + } + }, [stateParam, onSuccess, onError]) + + return ( +