This commit is contained in:
Hailey
2024-07-31 15:17:37 -07:00
parent 102fe99078
commit ece9664856
2 changed files with 238 additions and 85 deletions
+1
View File
@@ -13,5 +13,6 @@ export type Gate =
| 'suggested_feeds_interstitial' | 'suggested_feeds_interstitial'
| 'suggested_follows_interstitial' | 'suggested_follows_interstitial'
| 'ungroup_follow_backs' | 'ungroup_follow_backs'
| 'use_oauth'
| 'videos' | 'videos'
| 'small_avi_thumb' | 'small_avi_thumb'
+154 -2
View File
@@ -10,6 +10,12 @@ import {
ComAtprotoServerCreateSession, ComAtprotoServerCreateSession,
ComAtprotoServerDescribeServer, ComAtprotoServerDescribeServer,
} from '@atproto/api' } from '@atproto/api'
import {
AuthorizeOptions,
BrowserOAuthClient,
LoginContinuedInParentWindowError,
OAuthAgent,
} from '@atproto/oauth-client-browser'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -21,6 +27,7 @@ import {logger} from '#/logger'
import {useSessionApi} from '#/state/session' import {useSessionApi} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useRequestNotificationsPermission} from 'lib/notifications/notifications' import {useRequestNotificationsPermission} from 'lib/notifications/notifications'
import {useGate} from 'lib/statsig/statsig'
import {useSetHasCheckedForStarterPack} from 'state/preferences/used-starter-packs' import {useSetHasCheckedForStarterPack} from 'state/preferences/used-starter-packs'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
@@ -36,6 +43,106 @@ import {FormContainer} from './FormContainer'
type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
const client = new BrowserOAuthClient({
plcDirectoryUrl: 'https://plc.directory',
handleResolver: 'https://bsky.social',
})
export function useOAuth(client: BrowserOAuthClient) {
const [agent, setAgent] = useState<null | OAuthAgent>(null)
const [loading, setLoading] = useState(true)
const clientRef = useRef<typeof client>()
React.useEffect(() => {
// In strict mode, we don't want to reinitialize the client if it's the same
if (clientRef.current === client) return
clientRef.current = client
setLoading(true)
setAgent(null)
client
.init()
.then(async r => {
if (clientRef.current !== client) return
setAgent(r?.agent || null)
})
.catch(err => {
console.error('Failed to init:', err)
if (clientRef.current !== client) return
if (err instanceof LoginContinuedInParentWindowError) return
setAgent(null)
})
.finally(() => {
if (clientRef.current !== client) return
setLoading(false)
})
}, [client])
React.useEffect(() => {
if (!agent) return
const clear = ({detail}: {detail: {sub: string}}) => {
if (detail.sub === agent.sub) {
setAgent(null)
}
}
client.addEventListener('deleted', clear)
return () => {
client.removeEventListener('deleted', clear)
}
}, [client, agent])
const signOut = React.useCallback(async () => {
if (!agent) return
setAgent(null)
setLoading(true)
try {
await agent.signOut()
} catch (err) {
console.error('Failed to clear credentials', err)
throw err
} finally {
setLoading(false)
}
}, [agent])
const signIn = React.useCallback(
async (input: string, options?: AuthorizeOptions) => {
// if (agent) return
setLoading(true)
try {
const agent = await client.signIn(input, options)
setAgent(agent)
} catch (err) {
console.error('Failed to login', err)
throw err
} finally {
setLoading(false)
}
},
[client],
)
return {
agent,
loading,
signedIn: agent != null,
signIn,
signOut,
}
}
export const LoginForm = ({ export const LoginForm = ({
error, error,
serviceUrl, serviceUrl,
@@ -73,11 +180,20 @@ export const LoginForm = ({
const {setShowLoggedOut} = useLoggedOutViewControls() const {setShowLoggedOut} = useLoggedOutViewControls()
const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack() const setHasCheckedForStarterPack = useSetHasCheckedForStarterPack()
const gate = useGate()
const oauthEnabled = __DEV__ || gate('use_oauth')
const oauth = useOAuth(client)
const [handle, setHandle] = React.useState('')
const onPressSelectService = React.useCallback(() => { const onPressSelectService = React.useCallback(() => {
Keyboard.dismiss() Keyboard.dismiss()
track('Signin:PressedSelectService') track('Signin:PressedSelectService')
}, [track]) }, [track])
const onPressNextOauth = async () => {
await oauth.signIn(handle)
}
const onPressNext = async () => { const onPressNext = async () => {
if (isProcessing) return if (isProcessing) return
Keyboard.dismiss() Keyboard.dismiss()
@@ -175,6 +291,40 @@ export const LoginForm = ({
return ( return (
<FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}> <FormContainer testID="loginForm" titleText={<Trans>Sign in</Trans>}>
{oauthEnabled ? (
<>
<TextField.LabelText>
<Trans>Handle</Trans>
</TextField.LabelText>
<View style={[a.gap_sm]}>
<TextField.Root>
<TextField.Icon icon={At} />
<TextField.Input
testID="loginUsernameInput"
label={_(msg`alice.test`)}
autoCapitalize="none"
autoFocus
autoCorrect={false}
autoComplete="username"
returnKeyType="next"
textContentType="username"
defaultValue={initialHandle || ''}
onChangeText={v => {
setHandle(v)
checkIsReady()
}}
onSubmitEditing={() => {
// @TODO submit
}}
blurOnSubmit={false} // prevents flickering due to onSubmitEditing going to next field
editable={!isProcessing}
accessibilityHint={_(msg`Input the handle for your account`)}
/>
</TextField.Root>
</View>
</>
) : (
<>
<View> <View>
<TextField.LabelText> <TextField.LabelText>
<Trans>Hosting provider</Trans> <Trans>Hosting provider</Trans>
@@ -260,6 +410,8 @@ export const LoginForm = ({
</TextField.Root> </TextField.Root>
</View> </View>
</View> </View>
</>
)}
{isAuthFactorTokenNeeded && ( {isAuthFactorTokenNeeded && (
<View> <View>
<TextField.LabelText> <TextField.LabelText>
@@ -325,7 +477,7 @@ export const LoginForm = ({
<Trans>Connecting...</Trans> <Trans>Connecting...</Trans>
</Text> </Text>
</> </>
) : isReady ? ( ) : isReady || oauthEnabled ? (
<Button <Button
testID="loginNextButton" testID="loginNextButton"
label={_(msg`Next`)} label={_(msg`Next`)}
@@ -333,7 +485,7 @@ export const LoginForm = ({
variant="solid" variant="solid"
color="primary" color="primary"
size="medium" size="medium"
onPress={onPressNext}> onPress={oauthEnabled ? onPressNextOauth : onPressNext}>
<ButtonText> <ButtonText>
<Trans>Next</Trans> <Trans>Next</Trans>
</ButtonText> </ButtonText>