mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Oauth does not work yet
This commit is contained in:
		| @@ -5,14 +5,16 @@ const client = async ({ | ||||
|   version = 'v1', | ||||
|   method, | ||||
|   instance, | ||||
|   instanceUrl, | ||||
|   endpoint, | ||||
|   headers, | ||||
|   query, | ||||
|   body | ||||
| }: { | ||||
|   version?: 'v1' | 'v2' | ||||
|   method: 'get' | 'post' | 'delete' | ||||
|   method: 'get' | 'post' | 'put' | 'delete' | ||||
|   instance: 'local' | 'remote' | ||||
|   instanceUrl?: string | ||||
|   endpoint: string | ||||
|   headers?: { [key: string]: string } | ||||
|   query?: { | ||||
| @@ -21,12 +23,13 @@ const client = async ({ | ||||
|   body?: FormData | ||||
| }): Promise<any> => { | ||||
|   const state: RootState['instanceInfo'] = store.getState().instanceInfo | ||||
|   const url = instanceUrl || store.getState().instanceInfo[instance] | ||||
|  | ||||
|   let response | ||||
|   // try { | ||||
|   response = await ky(endpoint, { | ||||
|     method: method, | ||||
|     prefixUrl: `https://${state[instance]}/api/${version}`, | ||||
|     prefixUrl: `https://${url}/api/${version}`, | ||||
|     searchParams: query, | ||||
|     headers: { | ||||
|       'Content-Type': 'application/json', | ||||
| @@ -42,14 +45,19 @@ const client = async ({ | ||||
|   //   return Promise.reject('ky error: ' + error.json()) | ||||
|   // } | ||||
|   console.log('upload done') | ||||
|   if (response.ok) { | ||||
|   if (response?.ok) { | ||||
|     console.log('returning ok') | ||||
|     return Promise.resolve({ | ||||
|       headers: response.headers, | ||||
|       body: await response.json() | ||||
|     }) | ||||
|   } 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) | ||||
|     return Promise.reject({ body: errorResponse.error }) | ||||
|   } | ||||
|   | ||||
| @@ -8,6 +8,11 @@ import sharedScreens from 'src/stacks/Shared/sharedScreens' | ||||
|  | ||||
| const Stack = createNativeStackNavigator() | ||||
|  | ||||
| export type ScreenMe = { | ||||
|   'Me-Base': undefined | ||||
|   'Me-Authentication': undefined | ||||
| } | ||||
|  | ||||
| const Me: React.FC = () => { | ||||
|   return ( | ||||
|     <Stack.Navigator> | ||||
|   | ||||
| @@ -2,15 +2,19 @@ import React from 'react' | ||||
| import { createNativeStackNavigator } from 'react-native-screens/native-stack' | ||||
| 
 | ||||
| import Instance from './Authentication/Instance' | ||||
| import Webview from './Authentication/Webview' | ||||
| 
 | ||||
| const Stack = createNativeStackNavigator() | ||||
| 
 | ||||
| export default function Base () { | ||||
| export type ScreenMeAuthentication = { | ||||
|   'Me-Authentication-Instance': undefined | ||||
| } | ||||
| 
 | ||||
| const Base = () => { | ||||
|   return ( | ||||
|     <Stack.Navigator> | ||||
|       <Stack.Screen name='Me-Authentication-Instance' component={Instance} /> | ||||
|       <Stack.Screen name='Me-Authentication-Webview' component={Webview} /> | ||||
|     </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> | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										113
									
								
								src/stacks/Me/Authentication/Instance.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/stacks/Me/Authentication/Instance.tsx
									
									
									
									
									
										Normal 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 | ||||
| @@ -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> | ||||
|   ) | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/stacks/Me/Base.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/stacks/Me/Base.tsx
									
									
									
									
									
										Normal 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 | ||||
| @@ -204,6 +204,12 @@ const PostToot: React.FC = () => { | ||||
|         formData.append('poll[expires_in]', postState.poll.expire) | ||||
|         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({ | ||||
|         method: 'post', | ||||
| @@ -220,7 +226,10 @@ const PostToot: React.FC = () => { | ||||
|               Alert.alert('发布成功', '', [ | ||||
|                 { | ||||
|                   text: '好的', | ||||
|                   onPress: () => navigation.goBack() | ||||
|                   onPress: () => { | ||||
|                     // clear homepage cache | ||||
|                     navigation.goBack() | ||||
|                   } | ||||
|                 } | ||||
|               ]) | ||||
|             } else { | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import { | ||||
|   ActionSheetIOS, | ||||
|   Keyboard, | ||||
|   Pressable, | ||||
|   ScrollView, | ||||
|   StyleSheet, | ||||
|   Text, | ||||
|   TextInput, | ||||
| @@ -166,80 +167,93 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => { | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <View style={{ flex: 1 }}> | ||||
|       <TextInput | ||||
|         style={[ | ||||
|           styles.textInput, | ||||
|           { | ||||
|             flex: postState.overlay ? 0 : 1, | ||||
|             minHeight: editorMinHeight + 14 | ||||
|           } | ||||
|         ]} | ||||
|         autoCapitalize='none' | ||||
|         autoCorrect={false} | ||||
|         autoFocus | ||||
|         enablesReturnKeyAutomatically | ||||
|         multiline | ||||
|         placeholder='想说点什么' | ||||
|         onChangeText={content => onChangeText({ content })} | ||||
|         onContentSizeChange={({ nativeEvent }) => { | ||||
|           setEditorMinHeight(nativeEvent.contentSize.height) | ||||
|         }} | ||||
|         onSelectionChange={({ | ||||
|           nativeEvent: { | ||||
|             selection: { start, end } | ||||
|           } | ||||
|         }) => { | ||||
|           postDispatch({ type: 'selection', payload: { start, end } }) | ||||
|         }} | ||||
|         scrollEnabled | ||||
|     <View style={styles.base}> | ||||
|       <ScrollView | ||||
|         style={styles.contentView} | ||||
|         alwaysBounceVertical={false} | ||||
|         keyboardDismissMode='interactive' | ||||
|       > | ||||
|         <Text>{postState.text.formatted}</Text> | ||||
|       </TextInput> | ||||
|       {postState.attachments.length > 0 && ( | ||||
|         <View style={styles.attachments}> | ||||
|           <PostAttachments postState={postState} postDispatch={postDispatch} /> | ||||
|         </View> | ||||
|       )} | ||||
|       {postState.poll.active && ( | ||||
|         <View style={styles.poll}> | ||||
|           <PostPoll postState={postState} postDispatch={postDispatch} /> | ||||
|         </View> | ||||
|       )} | ||||
|       {postState.overlay === 'suggestions' ? ( | ||||
|         <View style={styles.suggestions}> | ||||
|           <PostSuggestions | ||||
|             onChangeText={onChangeText} | ||||
|             postState={postState} | ||||
|             postDispatch={postDispatch} | ||||
|           /> | ||||
|         </View> | ||||
|       ) : ( | ||||
|         <></> | ||||
|       )} | ||||
|       {postState.overlay === 'emojis' ? ( | ||||
|         <View style={styles.emojis}> | ||||
|           <PostEmojis | ||||
|             onChangeText={onChangeText} | ||||
|             postState={postState} | ||||
|             postDispatch={postDispatch} | ||||
|           /> | ||||
|         </View> | ||||
|       ) : ( | ||||
|         <></> | ||||
|       )} | ||||
|         <TextInput | ||||
|           style={[ | ||||
|             styles.textInput | ||||
|             // { | ||||
|             //   flex: postState.overlay ? 0 : 1, | ||||
|             //   minHeight: editorMinHeight + 14 | ||||
|             // } | ||||
|           ]} | ||||
|           autoCapitalize='none' | ||||
|           autoCorrect={false} | ||||
|           autoFocus | ||||
|           enablesReturnKeyAutomatically | ||||
|           multiline | ||||
|           placeholder='想说点什么' | ||||
|           onChangeText={content => onChangeText({ content })} | ||||
|           onContentSizeChange={({ nativeEvent }) => { | ||||
|             setEditorMinHeight(nativeEvent.contentSize.height) | ||||
|           }} | ||||
|           onSelectionChange={({ | ||||
|             nativeEvent: { | ||||
|               selection: { start, end } | ||||
|             } | ||||
|           }) => { | ||||
|             postDispatch({ type: 'selection', payload: { start, end } }) | ||||
|           }} | ||||
|           scrollEnabled | ||||
|         > | ||||
|           <Text>{postState.text.formatted}</Text> | ||||
|         </TextInput> | ||||
|         {postState.attachments.length > 0 && ( | ||||
|           <View style={styles.attachments}> | ||||
|             <PostAttachments | ||||
|               postState={postState} | ||||
|               postDispatch={postDispatch} | ||||
|             /> | ||||
|           </View> | ||||
|         )} | ||||
|         {postState.poll.active && ( | ||||
|           <View style={styles.poll}> | ||||
|             <PostPoll postState={postState} postDispatch={postDispatch} /> | ||||
|           </View> | ||||
|         )} | ||||
|         {postState.overlay === 'suggestions' ? ( | ||||
|           <View style={styles.suggestions}> | ||||
|             <PostSuggestions | ||||
|               onChangeText={onChangeText} | ||||
|               postState={postState} | ||||
|               postDispatch={postDispatch} | ||||
|             /> | ||||
|           </View> | ||||
|         ) : ( | ||||
|           <></> | ||||
|         )} | ||||
|         {postState.overlay === 'emojis' ? ( | ||||
|           <View style={styles.emojis}> | ||||
|             <PostEmojis | ||||
|               onChangeText={onChangeText} | ||||
|               postState={postState} | ||||
|               postDispatch={postDispatch} | ||||
|             /> | ||||
|           </View> | ||||
|         ) : ( | ||||
|           <></> | ||||
|         )} | ||||
|       </ScrollView> | ||||
|       <Pressable style={styles.additions} onPress={() => Keyboard.dismiss()}> | ||||
|         <Feather | ||||
|           name='paperclip' | ||||
|           size={24} | ||||
|           color={postState.poll.active ? 'gray' : 'black'} | ||||
|           onPress={async () => | ||||
|             await addAttachments({ postState, postDispatch }) | ||||
|             !postState.poll.active && | ||||
|             (await addAttachments({ postState, postDispatch })) | ||||
|           } | ||||
|         /> | ||||
|         <Feather | ||||
|           name='bar-chart-2' | ||||
|           size={24} | ||||
|           color={postState.attachments.length > 0 ? 'gray' : 'black'} | ||||
|           onPress={() => | ||||
|             postState.attachments.length === 0 && | ||||
|             postDispatch({ | ||||
|               type: 'poll', | ||||
|               payload: { ...postState.poll, active: !postState.poll.active } | ||||
| @@ -295,18 +309,22 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => { | ||||
| // (PostEmojis as any).whyDidYouRender = true | ||||
|  | ||||
| const styles = StyleSheet.create({ | ||||
|   main: { | ||||
|   base: { | ||||
|     flex: 1 | ||||
|   }, | ||||
|   textInput: { | ||||
|   contentView: { | ||||
|     flex: 1, | ||||
|     backgroundColor: 'gray' | ||||
|   }, | ||||
|   textInput: { | ||||
|     backgroundColor: 'lightgray', | ||||
|     paddingBottom: 20 | ||||
|   }, | ||||
|   attachments: { | ||||
|     flex: 1, | ||||
|     height: 100 | ||||
|   }, | ||||
|   poll: { | ||||
|     flex: 1, | ||||
|     height: 100 | ||||
|   }, | ||||
|   suggestions: { | ||||
|   | ||||
							
								
								
									
										14
									
								
								src/stacks/common/instanceFetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/stacks/common/instanceFetch.ts
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user