about summary refs log tree commit diff
path: root/src/screens/Signup
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2024-03-20 17:25:08 -0500
committerEric Bailey <git@esb.lol>2024-03-20 17:25:08 -0500
commit19fab671a3b11daa73a169b99752a4d2ba9e0166 (patch)
treef173c36f32482bf2a0fd9f91f429d3eb40e79059 /src/screens/Signup
parent58588efceadb030fa83367fb71dbecfce7b828d3 (diff)
downloadvoidsky-19fab671a3b11daa73a169b99752a4d2ba9e0166.tar.zst
Move some things around
Diffstat (limited to 'src/screens/Signup')
-rw-r--r--src/screens/Signup/StepCaptcha/CaptchaWebView.tsx87
-rw-r--r--src/screens/Signup/StepCaptcha/CaptchaWebView.web.tsx61
-rw-r--r--src/screens/Signup/StepCaptcha/index.tsx (renamed from src/screens/Signup/StepCaptcha.tsx)2
-rw-r--r--src/screens/Signup/StepInfo/Policies.tsx97
-rw-r--r--src/screens/Signup/StepInfo/index.tsx (renamed from src/screens/Signup/StepInfo.tsx)2
5 files changed, 247 insertions, 2 deletions
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 (
+    <WebView
+      source={{uri: url}}
+      javaScriptEnabled
+      style={styles.webview}
+      onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
+      onNavigationStateChange={onNavigationStateChange}
+      scrollEnabled={false}
+    />
+  )
+}
+
+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 (
+    <iframe
+      src={url}
+      style={styles.iframe}
+      id="captcha-iframe"
+      onLoad={onLoad}
+    />
+  )
+}
+
+const styles = StyleSheet.create({
+  iframe: {
+    flex: 1,
+    borderWidth: 0,
+    borderRadius: 10,
+    backgroundColor: 'transparent',
+  },
+})
diff --git a/src/screens/Signup/StepCaptcha.tsx b/src/screens/Signup/StepCaptcha/index.tsx
index 14da8ee93..311c697e7 100644
--- a/src/screens/Signup/StepCaptcha.tsx
+++ b/src/screens/Signup/StepCaptcha/index.tsx
@@ -6,9 +6,9 @@ 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 {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView'
 import {atoms as a, useTheme} from '#/alf'
 import {FormError} from '#/components/forms/FormError'
 
diff --git a/src/screens/Signup/StepInfo/Policies.tsx b/src/screens/Signup/StepInfo/Policies.tsx
new file mode 100644
index 000000000..8a656203f
--- /dev/null
+++ b/src/screens/Signup/StepInfo/Policies.tsx
@@ -0,0 +1,97 @@
+import React from 'react'
+import {View} from 'react-native'
+import {ComAtprotoServerDescribeServer} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {atoms as a, useTheme} from '#/alf'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+import {InlineLink} from '#/components/Link'
+import {Text} from '#/components/Typography'
+
+export const Policies = ({
+  serviceDescription,
+  needsGuardian,
+  under13,
+}: {
+  serviceDescription: ComAtprotoServerDescribeServer.OutputSchema
+  needsGuardian: boolean
+  under13: boolean
+}) => {
+  const t = useTheme()
+  const {_} = useLingui()
+
+  if (!serviceDescription) {
+    return <View />
+  }
+
+  const tos = validWebLink(serviceDescription.links?.termsOfService)
+  const pp = validWebLink(serviceDescription.links?.privacyPolicy)
+
+  if (!tos && !pp) {
+    return (
+      <View style={[a.flex_row, a.align_center, a.gap_xs]}>
+        <CircleInfo size="md" fill={t.atoms.text_contrast_low.color} />
+
+        <Text style={[t.atoms.text_contrast_medium]}>
+          <Trans>
+            This service has not provided terms of service or a privacy policy.
+          </Trans>
+        </Text>
+      </View>
+    )
+  }
+
+  const els = []
+  if (tos) {
+    els.push(
+      <InlineLink key="tos" to={tos}>
+        {_(msg`Terms of Service`)}
+      </InlineLink>,
+    )
+  }
+  if (pp) {
+    els.push(
+      <InlineLink key="pp" to={pp}>
+        {_(msg`Privacy Policy`)}
+      </InlineLink>,
+    )
+  }
+  if (els.length === 2) {
+    els.splice(
+      1,
+      0,
+      <Text key="and" style={[t.atoms.text_contrast_medium]}>
+        {' '}
+        and{' '}
+      </Text>,
+    )
+  }
+
+  return (
+    <View style={[a.gap_sm]}>
+      <Text style={[a.leading_snug, t.atoms.text_contrast_medium]}>
+        <Trans>By creating an account you agree to the {els}.</Trans>
+      </Text>
+
+      {under13 ? (
+        <Text style={[a.font_bold, a.leading_snug, t.atoms.text_contrast_high]}>
+          You must be 13 years of age or older to sign up.
+        </Text>
+      ) : needsGuardian ? (
+        <Text style={[a.font_bold, a.leading_snug, t.atoms.text_contrast_high]}>
+          <Trans>
+            If you are not yet an adult according to the laws of your country,
+            your parent or legal guardian must read these Terms on your behalf.
+          </Trans>
+        </Text>
+      ) : undefined}
+    </View>
+  )
+}
+
+function validWebLink(url?: string): string | undefined {
+  return url && (url.startsWith('http://') || url.startsWith('https://'))
+    ? url
+    : undefined
+}
diff --git a/src/screens/Signup/StepInfo.tsx b/src/screens/Signup/StepInfo/index.tsx
index 784d9e017..136592a0b 100644
--- a/src/screens/Signup/StepInfo.tsx
+++ b/src/screens/Signup/StepInfo/index.tsx
@@ -4,9 +4,9 @@ import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {logger} from '#/logger'
-import {Policies} from 'view/com/auth/create/Policies'
 import {ScreenTransition} from '#/screens/Login/ScreenTransition'
 import {is13, is18, useSignupContext} from '#/screens/Signup/state'
+import {Policies} from '#/screens/Signup/StepInfo/Policies'
 import {atoms as a} from '#/alf'
 import * as DateField from '#/components/forms/DateField'
 import {FormError} from '#/components/forms/FormError'