mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	
							
								
								
									
										34
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								src/App.tsx
									
									
									
									
									
								
							| @@ -10,13 +10,7 @@ import log from '@utils/startup/log' | |||||||
| import netInfo from '@utils/startup/netInfo' | import netInfo from '@utils/startup/netInfo' | ||||||
| import push from '@utils/startup/push' | import push from '@utils/startup/push' | ||||||
| import sentry from '@utils/startup/sentry' | import sentry from '@utils/startup/sentry' | ||||||
| import { storage } from '@utils/storage' | import { getGlobalStorage, setAccount, setGlobalStorage } from '@utils/storage/actions' | ||||||
| import { |  | ||||||
|   getGlobalStorage, |  | ||||||
|   removeAccount, |  | ||||||
|   setAccount, |  | ||||||
|   setGlobalStorage |  | ||||||
| } from '@utils/storage/actions' |  | ||||||
| import { migrateFromAsyncStorage, versionStorageGlobal } from '@utils/storage/migrations/toMMKV' | import { migrateFromAsyncStorage, versionStorageGlobal } from '@utils/storage/migrations/toMMKV' | ||||||
| import ThemeManager from '@utils/styles/ThemeManager' | import ThemeManager from '@utils/styles/ThemeManager' | ||||||
| import * as Localization from 'expo-localization' | import * as Localization from 'expo-localization' | ||||||
| @@ -24,7 +18,6 @@ import * as SplashScreen from 'expo-splash-screen' | |||||||
| import React, { useCallback, useEffect, useState } from 'react' | import React, { useCallback, useEffect, useState } from 'react' | ||||||
| import { LogBox, Platform } from 'react-native' | import { LogBox, Platform } from 'react-native' | ||||||
| import { GestureHandlerRootView } from 'react-native-gesture-handler' | import { GestureHandlerRootView } from 'react-native-gesture-handler' | ||||||
| import { MMKV } from 'react-native-mmkv' |  | ||||||
| import { SafeAreaProvider } from 'react-native-safe-area-context' | import { SafeAreaProvider } from 'react-native-safe-area-context' | ||||||
| import { enableFreeze } from 'react-native-screens' | import { enableFreeze } from 'react-native-screens' | ||||||
| import i18n from './i18n' | import i18n from './i18n' | ||||||
| @@ -36,6 +29,7 @@ Platform.select({ | |||||||
|  |  | ||||||
| dev() | dev() | ||||||
| sentry() | sentry() | ||||||
|  | netInfo() | ||||||
| audio() | audio() | ||||||
| push() | push() | ||||||
| enableFreeze(true) | enableFreeze(true) | ||||||
| @@ -46,7 +40,6 @@ SplashScreen.preventAutoHideAsync() | |||||||
| const App: React.FC = () => { | const App: React.FC = () => { | ||||||
|   log('log', 'App', 'rendering App') |   log('log', 'App', 'rendering App') | ||||||
|   const [appIsReady, setAppIsReady] = useState(false) |   const [appIsReady, setAppIsReady] = useState(false) | ||||||
|   const [localCorrupt, setLocalCorrupt] = useState<string>() |  | ||||||
|  |  | ||||||
|   const [hasMigrated, setHasMigrated] = useState<boolean>(versionStorageGlobal !== undefined) |   const [hasMigrated, setHasMigrated] = useState<boolean>(versionStorageGlobal !== undefined) | ||||||
|  |  | ||||||
| @@ -61,36 +54,19 @@ const App: React.FC = () => { | |||||||
|         log('log', 'App', 'loading from MMKV') |         log('log', 'App', 'loading from MMKV') | ||||||
|         const account = getGlobalStorage.string('account.active') |         const account = getGlobalStorage.string('account.active') | ||||||
|         if (account) { |         if (account) { | ||||||
|           const storageAccount = new MMKV({ id: account }) |           await setAccount(account) | ||||||
|           const token = storageAccount.getString('auth.token') |  | ||||||
|           if (token) { |  | ||||||
|             log('log', 'App', `Binding storage of ${account}`) |  | ||||||
|             storage.account = storageAccount |  | ||||||
|           } else { |  | ||||||
|             log('log', 'App', `Token not found for ${account}`) |  | ||||||
|             removeAccount(account) |  | ||||||
|           } |  | ||||||
|         } else { |         } else { | ||||||
|           log('log', 'App', 'No active account available') |           log('log', 'App', 'No active account available') | ||||||
|           const accounts = getGlobalStorage.object('accounts') |           const accounts = getGlobalStorage.object('accounts') | ||||||
|           if (accounts?.length) { |           if (accounts?.length) { | ||||||
|             log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`) |             log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`) | ||||||
|             setAccount(accounts[accounts.length - 1]) |             await setAccount(accounts[accounts.length - 1]) | ||||||
|           } else { |           } else { | ||||||
|             setGlobalStorage('account.active', undefined) |             setGlobalStorage('account.active', undefined) | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       let netInfoRes = undefined |  | ||||||
|       try { |  | ||||||
|         netInfoRes = await netInfo() |  | ||||||
|       } catch {} |  | ||||||
|  |  | ||||||
|       if (netInfoRes && netInfoRes.corrupted && netInfoRes.corrupted.length) { |  | ||||||
|         setLocalCorrupt(netInfoRes.corrupted) |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       log('log', 'App', `locale: ${Localization.locale}`) |       log('log', 'App', `locale: ${Localization.locale}`) | ||||||
|       const language = getLanguage() |       const language = getLanguage() | ||||||
|       if (!language) { |       if (!language) { | ||||||
| @@ -124,7 +100,7 @@ const App: React.FC = () => { | |||||||
|           <ActionSheetProvider> |           <ActionSheetProvider> | ||||||
|             <AccessibilityManager> |             <AccessibilityManager> | ||||||
|               <ThemeManager> |               <ThemeManager> | ||||||
|                 <Screens localCorrupt={localCorrupt} /> |                 <Screens /> | ||||||
|               </ThemeManager> |               </ThemeManager> | ||||||
|             </AccessibilityManager> |             </AccessibilityManager> | ||||||
|           </ActionSheetProvider> |           </ActionSheetProvider> | ||||||
|   | |||||||
| @@ -193,10 +193,12 @@ const ComponentInstance: React.FC<Props> = ({ | |||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   const scopes = featureCheck('deprecate_auth_follow') |   const processUpdate = useCallback(() => { | ||||||
|  |     const scopes = () => | ||||||
|  |       featureCheck('deprecate_auth_follow', instanceQuery.data?.version) | ||||||
|         ? ['read', 'write', 'push'] |         ? ['read', 'write', 'push'] | ||||||
|         : ['read', 'write', 'follow', 'push'] |         : ['read', 'write', 'follow', 'push'] | ||||||
|   const processUpdate = useCallback(() => { |  | ||||||
|     if (domain) { |     if (domain) { | ||||||
|       const accounts = getGlobalStorage.object('accounts') |       const accounts = getGlobalStorage.object('accounts') | ||||||
|       if (accounts?.filter(account => account.startsWith(`${domain}/`)).length) { |       if (accounts?.filter(account => account.startsWith(`${domain}/`)).length) { | ||||||
| @@ -210,15 +212,15 @@ const ComponentInstance: React.FC<Props> = ({ | |||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|               text: t('common:buttons.continue'), |               text: t('common:buttons.continue'), | ||||||
|               onPress: () => appsMutation.mutate({ domain, scopes }) |               onPress: () => appsMutation.mutate({ domain, scopes: scopes() }) | ||||||
|             } |             } | ||||||
|           ] |           ] | ||||||
|         ) |         ) | ||||||
|       } else { |       } else { | ||||||
|         appsMutation.mutate({ domain, scopes }) |         appsMutation.mutate({ domain, scopes: scopes() }) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }, [domain]) |   }, [domain, instanceQuery.data?.version]) | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <KeyboardAvoidingView |     <KeyboardAvoidingView | ||||||
|   | |||||||
| @@ -32,11 +32,7 @@ import { routingInstrumentation } from '../utils/startup/sentry' | |||||||
|  |  | ||||||
| const Stack = createNativeStackNavigator<RootStackParamList>() | const Stack = createNativeStackNavigator<RootStackParamList>() | ||||||
|  |  | ||||||
| export interface Props { | const Screens: React.FC = () => { | ||||||
|   localCorrupt?: string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const Screens: React.FC<Props> = ({ localCorrupt }) => { |  | ||||||
|   const { t, i18n } = useTranslation([ |   const { t, i18n } = useTranslation([ | ||||||
|     'common', |     'common', | ||||||
|     'screens', |     'screens', | ||||||
| @@ -64,24 +60,6 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => { | |||||||
|     return () => screenshotListener.remove() |     return () => screenshotListener.remove() | ||||||
|   }, []) |   }, []) | ||||||
|  |  | ||||||
|   // On launch display login credentials corrupt information |  | ||||||
|   useEffect(() => { |  | ||||||
|     const showLocalCorrect = () => { |  | ||||||
|       if (localCorrupt) { |  | ||||||
|         displayMessage({ |  | ||||||
|           message: t('screens:localCorrupt.message'), |  | ||||||
|           description: localCorrupt.length ? localCorrupt : undefined, |  | ||||||
|           type: 'danger' |  | ||||||
|         }) |  | ||||||
|         // @ts-ignore |  | ||||||
|         navigationRef.navigate('Screen-Tabs', { |  | ||||||
|           screen: 'Tab-Me' |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return showLocalCorrect() |  | ||||||
|   }, [localCorrupt]) |  | ||||||
|  |  | ||||||
|   // Lazily update users's preferences, for e.g. composing default visibility |   // Lazily update users's preferences, for e.g. composing default visibility | ||||||
|   useInstanceQuery({ options: { enabled: !!accountActive } }) |   useInstanceQuery({ options: { enabled: !!accountActive } }) | ||||||
|   useProfileQuery({ options: { enabled: !!accountActive } }) |   useProfileQuery({ options: { enabled: !!accountActive } }) | ||||||
|   | |||||||
| @@ -51,8 +51,6 @@ const features = [ | |||||||
|   } |   } | ||||||
| ] | ] | ||||||
|  |  | ||||||
| export const featureCheck = (feature: string): boolean => { | export const featureCheck = (feature: string, v?: string): boolean => | ||||||
|   const version = getAccountStorage.string('version') |   (features.find(f => f.feature === feature)?.version || 999) <= | ||||||
|   return !!features.filter(f => f.feature === feature).filter(f => parseFloat(version) >= f.version) |   parseFloat(v || getAccountStorage.string('version')) | ||||||
|     ?.length |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,67 +1,15 @@ | |||||||
| import NetInfo from '@react-native-community/netinfo' | import NetInfo from '@react-native-community/netinfo' | ||||||
| import { onlineManager } from '@tanstack/react-query' | import { onlineManager } from '@tanstack/react-query' | ||||||
| import apiInstance from '@utils/api/instance' |  | ||||||
| import { storage } from '@utils/storage' |  | ||||||
| import { getAccountStorage, removeAccount, setAccountStorage } from '@utils/storage/actions' |  | ||||||
| import log from './log' | import log from './log' | ||||||
|  |  | ||||||
| const netInfo = async (): Promise<{ | const netInfo = () => { | ||||||
|   connected?: boolean |  | ||||||
|   corrupted?: string |  | ||||||
| } | void> => { |  | ||||||
|   log('log', 'netInfo', 'initializing') |   log('log', 'netInfo', 'initializing') | ||||||
|  |  | ||||||
|   const netInfo = await NetInfo.fetch() |  | ||||||
|  |  | ||||||
|   onlineManager.setEventListener(setOnline => { |   onlineManager.setEventListener(setOnline => { | ||||||
|     return NetInfo.addEventListener(state => { |     return NetInfo.addEventListener(state => { | ||||||
|       setOnline(!!state.isConnected) |       setOnline(!!state.isConnected) | ||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   if (netInfo.isConnected) { |  | ||||||
|     log('log', 'netInfo', 'network connected') |  | ||||||
|     if (storage.account) { |  | ||||||
|       const domain = getAccountStorage.string('auth.domain') |  | ||||||
|       const id = getAccountStorage.string('auth.account.id') |  | ||||||
|       const account = `${domain}/${id}` |  | ||||||
|       log('log', 'netInfo', 'checking locally stored credentials') |  | ||||||
|  |  | ||||||
|       let resVerify: Mastodon.Account |  | ||||||
|       try { |  | ||||||
|         resVerify = await apiInstance<Mastodon.Account>({ |  | ||||||
|           method: 'get', |  | ||||||
|           url: `accounts/verify_credentials` |  | ||||||
|         }).then(res => res.body) |  | ||||||
|       } catch (error: any) { |  | ||||||
|         log('error', 'netInfo', 'local credential check failed') |  | ||||||
|         if (error?.status && error.status == 401) { |  | ||||||
|           removeAccount(account) |  | ||||||
|         } |  | ||||||
|         return Promise.resolve({ corrupted: error.data?.error }) |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       log('log', 'netInfo', 'local credential check passed') |  | ||||||
|       if (resVerify.id !== id) { |  | ||||||
|         log('error', 'netInfo', 'local id does not match remote id') |  | ||||||
|         removeAccount(account) |  | ||||||
|         return Promise.resolve({ connected: true, corrupted: '' }) |  | ||||||
|       } else { |  | ||||||
|         setAccountStorage([ |  | ||||||
|           { key: 'auth.account.acct', value: resVerify.acct }, |  | ||||||
|           { key: 'auth.account.avatar_static', value: resVerify.avatar_static } |  | ||||||
|         ]) |  | ||||||
|  |  | ||||||
|         return Promise.resolve({ connected: true }) |  | ||||||
|       } |  | ||||||
|     } else { |  | ||||||
|       log('log', 'netInfo', 'no local credential found') |  | ||||||
|       return Promise.resolve() |  | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     log('warn', 'netInfo', 'network not connected') |  | ||||||
|     return Promise.resolve() |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export default netInfo | export default netInfo | ||||||
|   | |||||||
| @@ -1,4 +1,9 @@ | |||||||
|  | import { displayMessage } from '@components/Message' | ||||||
|  | import i18n from '@i18n/index' | ||||||
|  | import apiGeneral from '@utils/api/general' | ||||||
|  | import navigationRef from '@utils/navigation/navigationRef' | ||||||
| import { queryClient } from '@utils/queryHooks' | import { queryClient } from '@utils/queryHooks' | ||||||
|  | import log from '@utils/startup/log' | ||||||
| import { storage } from '@utils/storage' | import { storage } from '@utils/storage' | ||||||
| import { Platform } from 'react-native' | import { Platform } from 'react-native' | ||||||
| import { | import { | ||||||
| @@ -222,10 +227,72 @@ export const generateAccountKey = ({ | |||||||
| }) => `${domain}/${id}` | }) => `${domain}/${id}` | ||||||
|  |  | ||||||
| export const setAccount = async (account: string) => { | export const setAccount = async (account: string) => { | ||||||
|   storage.account = new MMKV({ id: account }) |   const temp = new MMKV({ id: account }) | ||||||
|   setGlobalStorage('account.active', account) |   const token = temp.getString('auth.token') | ||||||
|  |   const domain = temp.getString('auth.domain') | ||||||
|  |  | ||||||
|  |   if (!token || !domain) { | ||||||
|  |     await removeAccount(account) | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   await apiGeneral<Mastodon.Account>({ | ||||||
|  |     method: 'get', | ||||||
|  |     domain, | ||||||
|  |     url: 'api/v1/accounts/verify_credentials', | ||||||
|  |     headers: { | ||||||
|  |       Authorization: `Bearer ${token}` | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  |     .then(res => res.body) | ||||||
|  |     .then(async a => { | ||||||
|  |       temp.set('auth.account.acct', a.acct) | ||||||
|  |       temp.set('auth.account.avatar_static', a.avatar_static) | ||||||
|  |  | ||||||
|  |       log('log', 'setAccount', `binding storage of ${account}`) | ||||||
|       await queryClient.resetQueries() |       await queryClient.resetQueries() | ||||||
|       queryClient.clear() |       queryClient.clear() | ||||||
|  |  | ||||||
|  |       storage.account = temp | ||||||
|  |       setGlobalStorage('account.active', account) | ||||||
|  |     }) | ||||||
|  |     .catch(async error => { | ||||||
|  |       if (error?.status && error.status == 401) { | ||||||
|  |         log('log', 'setAccount', `unauthorised ${account}`) | ||||||
|  |         await removeAccount(account) | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export const removeAccount = async (account: string) => { | ||||||
|  |   displayMessage({ | ||||||
|  |     message: i18n.t('screens:localCorrupt.message'), | ||||||
|  |     type: 'danger' | ||||||
|  |   }) | ||||||
|  |   // @ts-ignore | ||||||
|  |   navigationRef.navigate('Screen-Tabs', { screen: 'Tab-Me' }) | ||||||
|  |  | ||||||
|  |   const currAccounts: NonNullable<StorageGlobal['accounts']> = | ||||||
|  |     getGlobalStorage.object('accounts') || [] | ||||||
|  |   const nextAccounts: NonNullable<StorageGlobal['accounts']> = currAccounts.filter( | ||||||
|  |     a => a !== account | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   storage.global.set('accounts', JSON.stringify(nextAccounts)) | ||||||
|  |  | ||||||
|  |   if (nextAccounts.length) { | ||||||
|  |     log('log', 'removeAccount', `trying next account ${nextAccounts[nextAccounts.length - 1]}`) | ||||||
|  |     await setAccount(nextAccounts[nextAccounts.length - 1]) | ||||||
|  |   } else { | ||||||
|  |     log('log', 'removeAccount', 'setting to undefined') | ||||||
|  |     await queryClient.resetQueries() | ||||||
|  |     queryClient.clear() | ||||||
|  |  | ||||||
|  |     storage.account = undefined | ||||||
|  |     setGlobalStorage('account.active', undefined) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   new MMKV({ id: account }).clearAll() | ||||||
| } | } | ||||||
|  |  | ||||||
| export type ReadableAccountType = { | export type ReadableAccountType = { | ||||||
| @@ -263,25 +330,3 @@ export const getReadableAccounts = (withoutActive: boolean = false): ReadableAcc | |||||||
|     }) || [] |     }) || [] | ||||||
|   ).filter(a => a.acct.length) |   ).filter(a => a.acct.length) | ||||||
| } | } | ||||||
|  |  | ||||||
| export const removeAccount = async (account: string) => { |  | ||||||
|   const currAccounts: NonNullable<StorageGlobal['accounts']> = JSON.parse( |  | ||||||
|     storage.global.getString('accounts') || '[]' |  | ||||||
|   ) |  | ||||||
|   const nextAccounts: NonNullable<StorageGlobal['accounts']> = currAccounts.filter( |  | ||||||
|     a => a !== account |  | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   storage.global.set('accounts', JSON.stringify(nextAccounts)) |  | ||||||
|  |  | ||||||
|   if (nextAccounts.length) { |  | ||||||
|     await setAccount(nextAccounts[nextAccounts.length - 1]) |  | ||||||
|   } else { |  | ||||||
|     storage.account = undefined |  | ||||||
|     setGlobalStorage('account.active', undefined) |  | ||||||
|     queryClient.clear() |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const temp = new MMKV({ id: account }) |  | ||||||
|   temp.clearAll() |  | ||||||
| } |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user