mirror of https://github.com/tooot-app/app
Oauth does not work yet
This commit is contained in:
parent
aecb5c5b3d
commit
ab0062e73c
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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
|
|
@ -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>
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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
|
|
@ -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 {
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue