Oauth does not work yet

This commit is contained in:
Zhiyuan Zheng 2020-11-20 01:41:46 +01:00
parent aecb5c5b3d
commit ab0062e73c
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
11 changed files with 263 additions and 123 deletions

View File

@ -5,14 +5,16 @@ const client = async ({
version = 'v1', version = 'v1',
method, method,
instance, instance,
instanceUrl,
endpoint, endpoint,
headers, headers,
query, query,
body body
}: { }: {
version?: 'v1' | 'v2' version?: 'v1' | 'v2'
method: 'get' | 'post' | 'delete' method: 'get' | 'post' | 'put' | 'delete'
instance: 'local' | 'remote' instance: 'local' | 'remote'
instanceUrl?: string
endpoint: string endpoint: string
headers?: { [key: string]: string } headers?: { [key: string]: string }
query?: { query?: {
@ -21,12 +23,13 @@ const client = async ({
body?: FormData body?: FormData
}): Promise<any> => { }): Promise<any> => {
const state: RootState['instanceInfo'] = store.getState().instanceInfo const state: RootState['instanceInfo'] = store.getState().instanceInfo
const url = instanceUrl || store.getState().instanceInfo[instance]
let response let response
// try { // try {
response = await ky(endpoint, { response = await ky(endpoint, {
method: method, method: method,
prefixUrl: `https://${state[instance]}/api/${version}`, prefixUrl: `https://${url}/api/${version}`,
searchParams: query, searchParams: query,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -42,14 +45,19 @@ const client = async ({
// return Promise.reject('ky error: ' + error.json()) // return Promise.reject('ky error: ' + error.json())
// } // }
console.log('upload done') console.log('upload done')
if (response.ok) { if (response?.ok) {
console.log('returning ok') console.log('returning ok')
return Promise.resolve({ return Promise.resolve({
headers: response.headers, headers: response.headers,
body: await response.json() body: await response.json()
}) })
} else { } else {
const errorResponse = await response.json() let errorResponse
try {
errorResponse = await response.json()
} catch (error) {
return Promise.reject({ body: 'Nothing found' })
}
console.error(response.status + ': ' + errorResponse.error) console.error(response.status + ': ' + errorResponse.error)
return Promise.reject({ body: errorResponse.error }) return Promise.reject({ body: errorResponse.error })
} }

View File

@ -8,6 +8,11 @@ import sharedScreens from 'src/stacks/Shared/sharedScreens'
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator()
export type ScreenMe = {
'Me-Base': undefined
'Me-Authentication': undefined
}
const Me: React.FC = () => { const Me: React.FC = () => {
return ( return (
<Stack.Navigator> <Stack.Navigator>

View File

@ -2,15 +2,19 @@ import React from 'react'
import { createNativeStackNavigator } from 'react-native-screens/native-stack' import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import Instance from './Authentication/Instance' import Instance from './Authentication/Instance'
import Webview from './Authentication/Webview'
const Stack = createNativeStackNavigator() const Stack = createNativeStackNavigator()
export default function Base () { export type ScreenMeAuthentication = {
'Me-Authentication-Instance': undefined
}
const Base = () => {
return ( return (
<Stack.Navigator> <Stack.Navigator>
<Stack.Screen name='Me-Authentication-Instance' component={Instance} /> <Stack.Screen name='Me-Authentication-Instance' component={Instance} />
<Stack.Screen name='Me-Authentication-Webview' component={Webview} />
</Stack.Navigator> </Stack.Navigator>
) )
} }
export default Base

View File

@ -1,31 +0,0 @@
import React, { useState } from 'react'
import { Button, TextInput, View } from 'react-native'
export default function Instance ({ navigation }) {
const [instance, onChangeInstance] = useState()
return (
<View>
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={text => onChangeInstance(text)}
value={instance}
autoCapitalize='none'
autoCorrect={false}
clearButtonMode='unless-editing'
keyboardType='url'
textContentType='URL'
placeholder='输入服务器'
placeholderTextColor='#888888'
/>
<Button
title='登录'
onPress={() =>
navigation.navigate('Me-Authentication-Webview', {
instance: instance
})
}
/>
</View>
)
}

View File

@ -0,0 +1,113 @@
import React, { useCallback, useState } from 'react'
import { Button, Text, TextInput, View } from 'react-native'
import { StackNavigationProp } from '@react-navigation/stack'
import { useQuery } from 'react-query'
import { debounce } from 'lodash'
import { instanceFetch } from 'src/stacks/common/instanceFetch'
import { ScreenMeAuthentication } from '../Authentication'
import client from 'src/api/client'
import * as AppAuth from 'expo-app-auth'
const Instance: React.FC = () => {
const [instance, setInstance] = useState('')
const { isSuccess, refetch, data } = useQuery(
['Instance', { instance }],
instanceFetch,
{
enabled: false,
retry: false
}
)
const onChangeText = useCallback(
debounce(
text => {
setInstance(text)
refetch()
},
1000,
{
leading: true
}
),
[]
)
const signInAsync = async (id: string) => {
let authState = await AppAuth.authAsync({
issuer: `https://${instance}`,
scopes: ['read', 'write', 'follow', 'push'],
clientId: id,
redirectUrl: 'exp://127.0.0.1:19000',
serviceConfiguration: {
authorizationEndpoint: `https://${instance}/oauth/authorize`,
revocationEndpoint: `https://${instance}/oauth/revoke`,
tokenEndpoint: `https://${instance}/oauth/token`
},
additionalParameters: {
response_type: 'code'
}
})
console.log(authState)
return authState
}
const oauthCreateApplication = async () => {
const formData = new FormData()
formData.append('client_name', 'test_dudu')
formData.append('redirect_uris', 'exp://127.0.0.1:19000')
formData.append('scopes', 'read write follow push')
const res = await client({
method: 'post',
instance: 'remote',
instanceUrl: instance,
endpoint: `apps`,
body: formData
})
if (res.body?.client_id.length > 0) {
return Promise.resolve(res.body)
} else {
return Promise.reject()
}
}
const oauthFlow = async () => {
const applicationData = await oauthCreateApplication()
if (applicationData.client_id.length > 0) {
await signInAsync(applicationData.client_id)
} else {
console.error('Application data error')
}
}
return (
<View>
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={onChangeText}
autoCapitalize='none'
autoCorrect={false}
clearButtonMode='unless-editing'
keyboardType='url'
textContentType='URL'
placeholder='输入服务器'
placeholderTextColor='#888888'
/>
<Button
title='登录'
disabled={!data?.uri}
onPress={async () => await oauthFlow()}
/>
{isSuccess && data && data.uri && (
<View>
<Text>{data.title}</Text>
</View>
)}
</View>
)
}
export default Instance

View File

@ -1,9 +0,0 @@
import React from 'react'
import { Button, View } from 'react-native'
import * as AppAuth from 'expo-app-auth'
export default function Webview ({ navigation, route }) {
const { instance } = route.params
return <View></View>
}

View File

@ -1,10 +0,0 @@
import React from 'react'
import { Button, View } from 'react-native'
export default function Base ({ navigation: { navigate } }) {
return (
<View>
<Button title='登录' onPress={() => navigate('Me-Authentication')} />
</View>
)
}

19
src/stacks/Me/Base.tsx Normal file
View File

@ -0,0 +1,19 @@
import React from 'react'
import { Button, View } from 'react-native'
import { StackNavigationProp } from '@react-navigation/stack'
import { ScreenMe } from '../Me'
export interface Props {
navigation: StackNavigationProp<ScreenMe, 'Me-Base'>
}
const Base: React.FC<Props> = ({ navigation: { navigate } }) => {
return (
<View>
<Button title='登录' onPress={() => navigate('Me-Authentication')} />
</View>
)
}
export default Base

View File

@ -204,6 +204,12 @@ const PostToot: React.FC = () => {
formData.append('poll[expires_in]', postState.poll.expire) formData.append('poll[expires_in]', postState.poll.expire)
formData.append('poll[multiple]', postState.poll.multiple.toString()) formData.append('poll[multiple]', postState.poll.multiple.toString())
} }
if (postState.attachments.length > 0) {
postState.attachments.forEach(attachment =>
formData.append('media_ids[]', attachment.id)
)
}
formData.append('visibility', postState.visibility)
client({ client({
method: 'post', method: 'post',
@ -220,7 +226,10 @@ const PostToot: React.FC = () => {
Alert.alert('发布成功', '', [ Alert.alert('发布成功', '', [
{ {
text: '好的', text: '好的',
onPress: () => navigation.goBack() onPress: () => {
// clear homepage cache
navigation.goBack()
}
} }
]) ])
} else { } else {

View File

@ -9,6 +9,7 @@ import {
ActionSheetIOS, ActionSheetIOS,
Keyboard, Keyboard,
Pressable, Pressable,
ScrollView,
StyleSheet, StyleSheet,
Text, Text,
TextInput, TextInput,
@ -166,14 +167,19 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
} }
return ( return (
<View style={{ flex: 1 }}> <View style={styles.base}>
<ScrollView
style={styles.contentView}
alwaysBounceVertical={false}
keyboardDismissMode='interactive'
>
<TextInput <TextInput
style={[ style={[
styles.textInput, styles.textInput
{ // {
flex: postState.overlay ? 0 : 1, // flex: postState.overlay ? 0 : 1,
minHeight: editorMinHeight + 14 // minHeight: editorMinHeight + 14
} // }
]} ]}
autoCapitalize='none' autoCapitalize='none'
autoCorrect={false} autoCorrect={false}
@ -198,7 +204,10 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
</TextInput> </TextInput>
{postState.attachments.length > 0 && ( {postState.attachments.length > 0 && (
<View style={styles.attachments}> <View style={styles.attachments}>
<PostAttachments postState={postState} postDispatch={postDispatch} /> <PostAttachments
postState={postState}
postDispatch={postDispatch}
/>
</View> </View>
)} )}
{postState.poll.active && ( {postState.poll.active && (
@ -228,18 +237,23 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
) : ( ) : (
<></> <></>
)} )}
</ScrollView>
<Pressable style={styles.additions} onPress={() => Keyboard.dismiss()}> <Pressable style={styles.additions} onPress={() => Keyboard.dismiss()}>
<Feather <Feather
name='paperclip' name='paperclip'
size={24} size={24}
color={postState.poll.active ? 'gray' : 'black'}
onPress={async () => onPress={async () =>
await addAttachments({ postState, postDispatch }) !postState.poll.active &&
(await addAttachments({ postState, postDispatch }))
} }
/> />
<Feather <Feather
name='bar-chart-2' name='bar-chart-2'
size={24} size={24}
color={postState.attachments.length > 0 ? 'gray' : 'black'}
onPress={() => onPress={() =>
postState.attachments.length === 0 &&
postDispatch({ postDispatch({
type: 'poll', type: 'poll',
payload: { ...postState.poll, active: !postState.poll.active } payload: { ...postState.poll, active: !postState.poll.active }
@ -295,18 +309,22 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
// (PostEmojis as any).whyDidYouRender = true // (PostEmojis as any).whyDidYouRender = true
const styles = StyleSheet.create({ const styles = StyleSheet.create({
main: { base: {
flex: 1 flex: 1
}, },
textInput: { contentView: {
flex: 1,
backgroundColor: 'gray' backgroundColor: 'gray'
}, },
textInput: {
backgroundColor: 'lightgray',
paddingBottom: 20
},
attachments: { attachments: {
flex: 1, flex: 1,
height: 100 height: 100
}, },
poll: { poll: {
flex: 1,
height: 100 height: 100
}, },
suggestions: { suggestions: {

View File

@ -0,0 +1,14 @@
import client from 'src/api/client'
export const instanceFetch = async (
key: string,
{ instance }: { instance: string }
) => {
const res = await client({
method: 'get',
instance: 'remote',
instanceUrl: instance,
endpoint: `instance`
})
return Promise.resolve(res.body)
}