diff --git a/src/App.tsx b/src/App.tsx index 427e18ad..c1d671e2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,13 +10,7 @@ import log from '@utils/startup/log' import netInfo from '@utils/startup/netInfo' import push from '@utils/startup/push' import sentry from '@utils/startup/sentry' -import { storage } from '@utils/storage' -import { - getGlobalStorage, - removeAccount, - setAccount, - setGlobalStorage -} from '@utils/storage/actions' +import { getGlobalStorage, setAccount, setGlobalStorage } from '@utils/storage/actions' import { migrateFromAsyncStorage, versionStorageGlobal } from '@utils/storage/migrations/toMMKV' import ThemeManager from '@utils/styles/ThemeManager' 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 { LogBox, Platform } from 'react-native' import { GestureHandlerRootView } from 'react-native-gesture-handler' -import { MMKV } from 'react-native-mmkv' import { SafeAreaProvider } from 'react-native-safe-area-context' import { enableFreeze } from 'react-native-screens' import i18n from './i18n' @@ -36,6 +29,7 @@ Platform.select({ dev() sentry() +netInfo() audio() push() enableFreeze(true) @@ -46,7 +40,6 @@ SplashScreen.preventAutoHideAsync() const App: React.FC = () => { log('log', 'App', 'rendering App') const [appIsReady, setAppIsReady] = useState(false) - const [localCorrupt, setLocalCorrupt] = useState() const [hasMigrated, setHasMigrated] = useState(versionStorageGlobal !== undefined) @@ -61,36 +54,19 @@ const App: React.FC = () => { log('log', 'App', 'loading from MMKV') const account = getGlobalStorage.string('account.active') if (account) { - const storageAccount = new MMKV({ id: 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) - } + await setAccount(account) } else { log('log', 'App', 'No active account available') const accounts = getGlobalStorage.object('accounts') if (accounts?.length) { log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`) - setAccount(accounts[accounts.length - 1]) + await setAccount(accounts[accounts.length - 1]) } else { 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}`) const language = getLanguage() if (!language) { @@ -124,7 +100,7 @@ const App: React.FC = () => { - + diff --git a/src/components/Instance/index.tsx b/src/components/Instance/index.tsx index 9b53312d..5d8e829d 100644 --- a/src/components/Instance/index.tsx +++ b/src/components/Instance/index.tsx @@ -193,10 +193,12 @@ const ComponentInstance: React.FC = ({ } }) - const scopes = featureCheck('deprecate_auth_follow') - ? ['read', 'write', 'push'] - : ['read', 'write', 'follow', 'push'] const processUpdate = useCallback(() => { + const scopes = () => + featureCheck('deprecate_auth_follow', instanceQuery.data?.version) + ? ['read', 'write', 'push'] + : ['read', 'write', 'follow', 'push'] + if (domain) { const accounts = getGlobalStorage.object('accounts') if (accounts?.filter(account => account.startsWith(`${domain}/`)).length) { @@ -210,15 +212,15 @@ const ComponentInstance: React.FC = ({ }, { text: t('common:buttons.continue'), - onPress: () => appsMutation.mutate({ domain, scopes }) + onPress: () => appsMutation.mutate({ domain, scopes: scopes() }) } ] ) } else { - appsMutation.mutate({ domain, scopes }) + appsMutation.mutate({ domain, scopes: scopes() }) } } - }, [domain]) + }, [domain, instanceQuery.data?.version]) return ( () -export interface Props { - localCorrupt?: string -} - -const Screens: React.FC = ({ localCorrupt }) => { +const Screens: React.FC = () => { const { t, i18n } = useTranslation([ 'common', 'screens', @@ -64,24 +60,6 @@ const Screens: React.FC = ({ localCorrupt }) => { 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 useInstanceQuery({ options: { enabled: !!accountActive } }) useProfileQuery({ options: { enabled: !!accountActive } }) diff --git a/src/utils/helpers/featureCheck.ts b/src/utils/helpers/featureCheck.ts index ff6f4310..83acf784 100644 --- a/src/utils/helpers/featureCheck.ts +++ b/src/utils/helpers/featureCheck.ts @@ -51,8 +51,6 @@ const features = [ } ] -export const featureCheck = (feature: string): boolean => { - const version = getAccountStorage.string('version') - return !!features.filter(f => f.feature === feature).filter(f => parseFloat(version) >= f.version) - ?.length -} +export const featureCheck = (feature: string, v?: string): boolean => + (features.find(f => f.feature === feature)?.version || 999) <= + parseFloat(v || getAccountStorage.string('version')) diff --git a/src/utils/startup/netInfo.ts b/src/utils/startup/netInfo.ts index d20e558f..800e8af7 100644 --- a/src/utils/startup/netInfo.ts +++ b/src/utils/startup/netInfo.ts @@ -1,67 +1,15 @@ import NetInfo from '@react-native-community/netinfo' 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' -const netInfo = async (): Promise<{ - connected?: boolean - corrupted?: string -} | void> => { +const netInfo = () => { log('log', 'netInfo', 'initializing') - const netInfo = await NetInfo.fetch() - onlineManager.setEventListener(setOnline => { return NetInfo.addEventListener(state => { 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({ - 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 diff --git a/src/utils/storage/actions.ts b/src/utils/storage/actions.ts index 584c4005..d0383e1f 100644 --- a/src/utils/storage/actions.ts +++ b/src/utils/storage/actions.ts @@ -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 log from '@utils/startup/log' import { storage } from '@utils/storage' import { Platform } from 'react-native' import { @@ -222,10 +227,72 @@ export const generateAccountKey = ({ }) => `${domain}/${id}` export const setAccount = async (account: string) => { - storage.account = new MMKV({ id: account }) - setGlobalStorage('account.active', account) - await queryClient.resetQueries() - queryClient.clear() + const temp = new MMKV({ id: account }) + const token = temp.getString('auth.token') + const domain = temp.getString('auth.domain') + + if (!token || !domain) { + await removeAccount(account) + return + } + + await apiGeneral({ + 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() + 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 = + getGlobalStorage.object('accounts') || [] + const nextAccounts: NonNullable = 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 = { @@ -263,25 +330,3 @@ export const getReadableAccounts = (withoutActive: boolean = false): ReadableAcc }) || [] ).filter(a => a.acct.length) } - -export const removeAccount = async (account: string) => { - const currAccounts: NonNullable = JSON.parse( - storage.global.getString('accounts') || '[]' - ) - const nextAccounts: NonNullable = 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() -}