From 19fab671a3b11daa73a169b99752a4d2ba9e0166 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 20 Mar 2024 17:25:08 -0500 Subject: Move some things around --- src/screens/Signup/StepCaptcha.tsx | 95 ------- src/screens/Signup/StepCaptcha/CaptchaWebView.tsx | 87 ++++++ .../Signup/StepCaptcha/CaptchaWebView.web.tsx | 61 +++++ src/screens/Signup/StepCaptcha/index.tsx | 95 +++++++ src/screens/Signup/StepInfo.tsx | 146 ---------- src/screens/Signup/StepInfo/Policies.tsx | 97 +++++++ src/screens/Signup/StepInfo/index.tsx | 146 ++++++++++ src/view/com/auth/create/CaptchaWebView.tsx | 86 ------ src/view/com/auth/create/CaptchaWebView.web.tsx | 61 ----- src/view/com/auth/create/Policies.tsx | 97 ------- src/view/com/auth/create/state.ts | 298 --------------------- 11 files changed, 486 insertions(+), 783 deletions(-) delete mode 100644 src/screens/Signup/StepCaptcha.tsx create mode 100644 src/screens/Signup/StepCaptcha/CaptchaWebView.tsx create mode 100644 src/screens/Signup/StepCaptcha/CaptchaWebView.web.tsx create mode 100644 src/screens/Signup/StepCaptcha/index.tsx delete mode 100644 src/screens/Signup/StepInfo.tsx create mode 100644 src/screens/Signup/StepInfo/Policies.tsx create mode 100644 src/screens/Signup/StepInfo/index.tsx delete mode 100644 src/view/com/auth/create/CaptchaWebView.tsx delete mode 100644 src/view/com/auth/create/CaptchaWebView.web.tsx delete mode 100644 src/view/com/auth/create/Policies.tsx delete mode 100644 src/view/com/auth/create/state.ts (limited to 'src') diff --git a/src/screens/Signup/StepCaptcha.tsx b/src/screens/Signup/StepCaptcha.tsx deleted file mode 100644 index 14da8ee93..000000000 --- a/src/screens/Signup/StepCaptcha.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react' -import {ActivityIndicator, StyleSheet, View} from 'react-native' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {nanoid} from 'nanoid/non-secure' - -import {createFullHandle} from '#/lib/strings/handles' -import {isWeb} from '#/platform/detection' -import {CaptchaWebView} from '#/view/com/auth/create/CaptchaWebView' -import {ScreenTransition} from '#/screens/Login/ScreenTransition' -import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' -import {atoms as a, useTheme} from '#/alf' -import {FormError} from '#/components/forms/FormError' - -const CAPTCHA_PATH = '/gate/signup' - -export function StepCaptcha() { - const {_} = useLingui() - const theme = useTheme() - const {state, dispatch} = useSignupContext() - const submit = useSubmitSignup({state, dispatch}) - - const [completed, setCompleted] = React.useState(false) - - const stateParam = React.useMemo(() => nanoid(15), []) - const url = React.useMemo(() => { - const newUrl = new URL(state.serviceUrl) - newUrl.pathname = CAPTCHA_PATH - newUrl.searchParams.set( - 'handle', - createFullHandle(state.handle, state.userDomain), - ) - newUrl.searchParams.set('state', stateParam) - newUrl.searchParams.set('colorScheme', theme.name) - - return newUrl.href - }, [state.serviceUrl, state.handle, state.userDomain, stateParam, theme.name]) - - const onSuccess = React.useCallback( - (code: string) => { - setCompleted(true) - submit(code) - }, - [submit], - ) - - const onError = React.useCallback(() => { - dispatch({ - type: 'setError', - value: _(msg`Error receiving captcha response.`), - }) - }, [_, dispatch]) - - return ( - - - - {!completed ? ( - - ) : ( - - )} - - - - - ) -} - -const styles = StyleSheet.create({ - error: { - borderRadius: 6, - marginTop: 10, - }, - // @ts-expect-error: Suppressing error due to incomplete `ViewStyle` type definition in react-native-web, missing `cursor` prop as discussed in https://github.com/necolas/react-native-web/issues/832. - touchable: { - ...(isWeb && {cursor: 'pointer'}), - }, - container: { - minHeight: 500, - width: '100%', - paddingBottom: 20, - overflow: 'hidden', - }, - center: { - alignItems: 'center', - justifyContent: 'center', - }, -}) diff --git a/src/screens/Signup/StepCaptcha/CaptchaWebView.tsx b/src/screens/Signup/StepCaptcha/CaptchaWebView.tsx new file mode 100644 index 000000000..50918c4ce --- /dev/null +++ b/src/screens/Signup/StepCaptcha/CaptchaWebView.tsx @@ -0,0 +1,87 @@ +import React from 'react' +import {StyleSheet} from 'react-native' +import {WebView, WebViewNavigation} from 'react-native-webview' +import {ShouldStartLoadRequest} from 'react-native-webview/lib/WebViewTypes' + +import {SignupState} from '#/screens/Signup/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, + state, + onSuccess, + onError, +}: { + url: string + stateParam: string + state?: SignupState + onSuccess: (code: string) => void + onError: () => void +}) { + const redirectHost = React.useMemo(() => { + if (!state?.serviceUrl) return 'bsky.app' + + return state?.serviceUrl && + new URL(state?.serviceUrl).host === 'staging.bsky.dev' + ? 'staging.bsky.app' + : 'bsky.app' + }, [state?.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/screens/Signup/StepCaptcha/CaptchaWebView.web.tsx b/src/screens/Signup/StepCaptcha/CaptchaWebView.web.tsx new file mode 100644 index 000000000..7791a58dd --- /dev/null +++ b/src/screens/Signup/StepCaptcha/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 ( +