add the clients

This commit is contained in:
Hailey
2025-07-17 17:58:25 -07:00
parent 08a1a806ef
commit aa2b1f0eec
6 changed files with 241 additions and 15 deletions
+1
View File
@@ -5,6 +5,7 @@ export type Gate =
| 'debug_show_feedcontext'
| 'debug_subscriptions'
| 'explore_show_suggested_feeds'
| 'oauth'
| 'old_postonboarding'
| 'onboarding_add_video_feed'
| 'post_threads_v2_unspecced'
+108 -14
View File
@@ -32,22 +32,13 @@ import {Ticket_Stroke2_Corner0_Rounded as Ticket} from '#/components/icons/Ticke
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {FormContainer} from './FormContainer'
import {useGate} from '#/lib/statsig/statsig'
import {isWeb} from '#/platform/detection'
import {getNativeOAuthClient, getWebOAuthClient} from '#/state/session/oauth'
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
export const LoginForm = ({
error,
serviceUrl,
serviceDescription,
initialHandle,
setError,
setServiceUrl,
onPressRetryConnect,
onPressBack,
onPressForgotPassword,
onAttemptSuccess,
onAttemptFailed,
}: {
interface LoginFormProps {
error: string
serviceUrl: string
serviceDescription: ServiceDescription | undefined
@@ -59,7 +50,110 @@ export const LoginForm = ({
onPressForgotPassword: () => void
onAttemptSuccess: () => void
onAttemptFailed: () => void
}) => {
}
export function LoginForm(props: LoginFormProps) {
const gate = useGate()
if (gate('oauth')) {
return <OAuthLoginForm {...props} />
} else {
return <LoginFormInner {...props} />
}
}
function OAuthLoginForm({error, initialHandle, onPressBack}: LoginFormProps) {
const {_} = useLingui()
const [isProcessing, setIsProcessing] = React.useState(false)
const identifierValueRef = useRef<string>(initialHandle || '')
const onPressNext = async () => {
setIsProcessing(true)
if (isWeb) {
const client = getWebOAuthClient()
await client.signIn(identifierValueRef.current)
} else {
const client = getNativeOAuthClient()
const res = await client.signIn(identifierValueRef.current)
// redirect after result
}
}
return (
<FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}>
<View>
<TextField.LabelText>
<Trans>Account</Trans>
</TextField.LabelText>
<View style={[a.gap_sm]}>
<TextField.Root>
<TextField.Icon icon={At} />
<TextField.Input
testID="loginUsernameInput"
label={_(msg`Username or email address`)}
autoCapitalize="none"
autoFocus
autoCorrect={false}
autoComplete="username"
returnKeyType="next"
textContentType="username"
defaultValue={initialHandle || ''}
onChangeText={v => {
identifierValueRef.current = v
}}
onSubmitEditing={() => {}}
blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field
editable={!isProcessing}
accessibilityHint={_(
msg`Enter the username or email address you used when you created your account`,
)}
/>
</TextField.Root>
</View>
</View>
<FormError error={error} />
<View style={[a.flex_row, a.align_center, a.pt_md]}>
<Button
label={_(msg`Back`)}
variant="solid"
color="secondary"
size="large"
onPress={onPressBack}>
<ButtonText>
<Trans>Back</Trans>
</ButtonText>
</Button>
<View style={a.flex_1} />
<Button
testID="loginNextButton"
label={_(msg`Login`)}
accessibilityHint={_(msg`Navigates to the login screen`)}
variant="solid"
color="primary"
size="large"
onPress={onPressNext}>
<ButtonText>
<Trans>Login</Trans>
</ButtonText>
{isProcessing && <ButtonIcon icon={Loader} />}
</Button>
</View>
</FormContainer>
)
}
const LoginFormInner = ({
error,
serviceUrl,
serviceDescription,
initialHandle,
setError,
setServiceUrl,
onPressRetryConnect,
onPressBack,
onPressForgotPassword,
onAttemptSuccess,
onAttemptFailed,
}: LoginFormProps) => {
const t = useTheme()
const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [isAuthFactorTokenNeeded, setIsAuthFactorTokenNeeded] =
+2
View File
@@ -21,6 +21,8 @@ import {
} from './moderation'
import {SessionAccount} from './types'
import {isSessionExpired, isSignupQueued} from './util'
import {BSKY_OAUTH_CLIENT} from './oauth'
import {ExpoOAuthClient} from 'expo-atproto-auth'
export function createPublicAgent() {
configureModerationForGuest() // Side effect but only relevant for tests
+51
View File
@@ -0,0 +1,51 @@
import {ExpoOAuthClient} from 'expo-atproto-auth'
import {BrowserOAuthClient} from '@atproto/oauth-client-browser'
import {OAuthClient} from '@atproto/oauth-client'
import {Platform} from 'react-native'
export const BSKY_OAUTH_CLIENT: OAuthClient =
Platform.OS === 'web' ? createWebOAuthClient() : createNativeOAuthClient()
export function createWebOAuthClient() {
return new BrowserOAuthClient({
clientMetadata: {
client_id: 'https://bsky.hailey.at/oauth-client-metadata.json',
client_name: 'Bluesky (Hailey Demo)',
client_uri: 'https://bsky.hailey.at',
redirect_uris: ['https://bsky.hailey.at/auth/callback'],
scope: 'atproto transition:generic',
token_endpoint_auth_method: 'none',
response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'],
application_type: 'web',
dpop_bound_access_tokens: true,
},
handleResolver: 'https://bsky.social',
})
}
export function createNativeOAuthClient() {
return new ExpoOAuthClient({
clientMetadata: {
client_id: 'https://bsky.hailey.at/oauth-client-metadata.native.json',
client_name: 'Bluesky Native App (Hailey Demo)',
client_uri: 'https://hailey.at',
redirect_uris: ['at.hailey:/auth/callback'],
scope: 'atproto transition:generic',
token_endpoint_auth_method: 'none',
response_types: ['code'],
grant_types: ['authorization_code', 'refresh_token'],
application_type: 'native',
dpop_bound_access_tokens: true,
},
handleResolver: 'https://bsky.social',
})
}
export function getNativeOAuthClient() {
return BSKY_OAUTH_CLIENT as ExpoOAuthClient
}
export function getWebOAuthClient() {
return BSKY_OAUTH_CLIENT as BrowserOAuthClient
}