mirror of
https://github.com/tooot-app/app
synced 2024-12-21 23:24:29 +01:00
Improve push error messaging
This commit is contained in:
parent
1a069d5acc
commit
748351026f
@ -1,3 +1,5 @@
|
||||
Enjoy toooting! This version includes following improvements and fixes:
|
||||
- Automatic setting detected language when tooting
|
||||
- Added notification for admins
|
||||
- Fix whole word filter matching
|
||||
- Fix tablet cannot delete toot drafts
|
@ -1,3 +1,5 @@
|
||||
toooting愉快!此版本包括以下改进和修复:
|
||||
- 自动识别发嘟语言
|
||||
- 新增管理员推送通知
|
||||
- 修复过滤整词功能
|
||||
- 修复平板不能删除草稿
|
@ -1,90 +0,0 @@
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { InstanceLatest } from '@utils/migrations/instances/migration'
|
||||
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
||||
import addInstance from '@utils/slices/instances/add'
|
||||
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
instanceDomain: string
|
||||
// Domain can be different than uri
|
||||
instance: Mastodon.Instance
|
||||
appData: InstanceLatest['appData']
|
||||
goBack?: boolean
|
||||
}
|
||||
|
||||
const InstanceAuth = React.memo(
|
||||
({ instanceDomain, instance, appData, goBack }: Props) => {
|
||||
const redirectUri = AuthSession.makeRedirectUri({
|
||||
native: 'tooot://instance-auth',
|
||||
useProxy: false
|
||||
})
|
||||
|
||||
const navigation = useNavigation<TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'>>()
|
||||
const queryClient = useQueryClient()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const deprecateAuthFollow = useSelector(checkInstanceFeature('deprecate_auth_follow'))
|
||||
const [request, response, promptAsync] = AuthSession.useAuthRequest(
|
||||
{
|
||||
clientId: appData.clientId,
|
||||
clientSecret: appData.clientSecret,
|
||||
scopes: deprecateAuthFollow
|
||||
? ['read', 'write', 'push']
|
||||
: ['read', 'write', 'follow', 'push'],
|
||||
redirectUri
|
||||
},
|
||||
{
|
||||
authorizationEndpoint: `https://${instanceDomain}/oauth/authorize`
|
||||
}
|
||||
)
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
if (request?.clientId) {
|
||||
await promptAsync({ browserPackage: await browserPackage() }).catch(e => console.log(e))
|
||||
}
|
||||
})()
|
||||
}, [request])
|
||||
useEffect(() => {
|
||||
;(async () => {
|
||||
if (response?.type === 'success') {
|
||||
const { accessToken } = await AuthSession.exchangeCodeAsync(
|
||||
{
|
||||
clientId: appData.clientId,
|
||||
clientSecret: appData.clientSecret,
|
||||
scopes: ['read', 'write', 'follow', 'push'],
|
||||
redirectUri,
|
||||
code: response.params.code,
|
||||
extraParams: {
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
},
|
||||
{
|
||||
tokenEndpoint: `https://${instanceDomain}/oauth/token`
|
||||
}
|
||||
)
|
||||
queryClient.clear()
|
||||
dispatch(
|
||||
addInstance({
|
||||
domain: instanceDomain,
|
||||
token: accessToken,
|
||||
instance,
|
||||
appData
|
||||
})
|
||||
)
|
||||
goBack && navigation.goBack()
|
||||
}
|
||||
})()
|
||||
}, [response])
|
||||
|
||||
return <></>
|
||||
},
|
||||
() => true
|
||||
)
|
||||
|
||||
export default InstanceAuth
|
@ -1,22 +1,27 @@
|
||||
import Button from '@components/Button'
|
||||
import Icon from '@components/Icon'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useAppsQuery } from '@utils/queryHooks/apps'
|
||||
import { redirectUri, useAppsMutation } from '@utils/queryHooks/apps'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { getInstances } from '@utils/slices/instancesSlice'
|
||||
import { checkInstanceFeature, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import { debounce } from 'lodash'
|
||||
import React, { RefObject, useCallback, useMemo, useState } from 'react'
|
||||
import React, { RefObject, useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Placeholder } from 'rn-placeholder'
|
||||
import InstanceAuth from './Instance/Auth'
|
||||
import InstanceInfo from './Instance/Info'
|
||||
import CustomText from './Text'
|
||||
import InstanceInfo from './Info'
|
||||
import CustomText from '../Text'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
||||
import queryClient from '@helpers/queryClient'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import addInstance from '@utils/slices/instances/add'
|
||||
|
||||
export interface Props {
|
||||
scrollViewRef?: RefObject<ScrollView>
|
||||
@ -31,30 +36,64 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation('componentInstance')
|
||||
const { colors, mode } = useTheme()
|
||||
const navigation = useNavigation<TabMeStackNavigationProp<'Tab-Me-Root' | 'Tab-Me-Switch'>>()
|
||||
|
||||
const [domain, setDomain] = useState<string>('')
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
const [domain, setDomain] = useState<string>()
|
||||
|
||||
const instanceQuery = useInstanceQuery({
|
||||
domain,
|
||||
options: { enabled: !!domain, retry: false }
|
||||
})
|
||||
const appsQuery = useAppsQuery({
|
||||
domain,
|
||||
options: { enabled: false, retry: false }
|
||||
})
|
||||
|
||||
const onChangeText = useCallback(
|
||||
debounce(
|
||||
text => {
|
||||
setDomain(text.replace(/^http(s)?\:\/\//i, ''))
|
||||
appsQuery.remove()
|
||||
const deprecateAuthFollow = useSelector(checkInstanceFeature('deprecate_auth_follow'))
|
||||
|
||||
const appsMutation = useAppsMutation({
|
||||
retry: false,
|
||||
onSuccess: async (data, variables) => {
|
||||
const clientId = data.client_id
|
||||
const clientSecret = data.client_secret
|
||||
|
||||
const discovery = { authorizationEndpoint: `https://${domain}/oauth/authorize` }
|
||||
|
||||
const request = new AuthSession.AuthRequest({
|
||||
clientId,
|
||||
clientSecret,
|
||||
scopes: deprecateAuthFollow
|
||||
? ['read', 'write', 'push']
|
||||
: ['read', 'write', 'follow', 'push'],
|
||||
redirectUri
|
||||
})
|
||||
await request.makeAuthUrlAsync(discovery)
|
||||
|
||||
const promptResult = await request.promptAsync(discovery)
|
||||
|
||||
if (promptResult?.type === 'success') {
|
||||
const { accessToken } = await AuthSession.exchangeCodeAsync(
|
||||
{
|
||||
clientId,
|
||||
clientSecret,
|
||||
scopes: ['read', 'write', 'follow', 'push'],
|
||||
redirectUri,
|
||||
code: promptResult.params.code,
|
||||
extraParams: { grant_type: 'authorization_code' }
|
||||
},
|
||||
1000,
|
||||
{ trailing: true }
|
||||
),
|
||||
[]
|
||||
{ tokenEndpoint: `https://${variables.domain}/oauth/token` }
|
||||
)
|
||||
queryClient.clear()
|
||||
dispatch(
|
||||
addInstance({
|
||||
domain,
|
||||
token: accessToken,
|
||||
instance: instanceQuery.data!,
|
||||
appData: { clientId, clientSecret }
|
||||
})
|
||||
)
|
||||
goBack && navigation.goBack()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const processUpdate = useCallback(() => {
|
||||
if (domain) {
|
||||
@ -66,39 +105,15 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.continue'),
|
||||
onPress: () => {
|
||||
appsQuery.refetch()
|
||||
}
|
||||
onPress: () => appsMutation.mutate({ domain })
|
||||
}
|
||||
])
|
||||
} else {
|
||||
appsQuery.refetch()
|
||||
appsMutation.mutate({ domain })
|
||||
}
|
||||
}
|
||||
}, [domain])
|
||||
|
||||
const requestAuth = useMemo(() => {
|
||||
if (
|
||||
domain &&
|
||||
instanceQuery.data?.uri &&
|
||||
appsQuery.data?.client_id &&
|
||||
appsQuery.data.client_secret
|
||||
) {
|
||||
return (
|
||||
<InstanceAuth
|
||||
key={Math.random()}
|
||||
instanceDomain={domain}
|
||||
instance={instanceQuery.data}
|
||||
appData={{
|
||||
clientId: appsQuery.data.client_id,
|
||||
clientSecret: appsQuery.data.client_secret
|
||||
}}
|
||||
goBack={goBack}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}, [domain, instanceQuery.data, appsQuery.data])
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={{ flex: 1 }}
|
||||
@ -145,7 +160,9 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
color: colors.primaryDefault,
|
||||
borderBottomColor: instanceQuery.isError ? colors.red : colors.border
|
||||
}}
|
||||
onChangeText={onChangeText}
|
||||
onChangeText={debounce(text => setDomain(text.replace(/^http(s)?\:\/\//i, '')), 1000, {
|
||||
trailing: true
|
||||
})}
|
||||
autoCapitalize='none'
|
||||
clearButtonMode='never'
|
||||
keyboardType='url'
|
||||
@ -176,7 +193,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
content={t('server.button')}
|
||||
onPress={processUpdate}
|
||||
disabled={!instanceQuery.data?.uri}
|
||||
loading={instanceQuery.isFetching || appsQuery.isFetching}
|
||||
loading={instanceQuery.isFetching || appsMutation.isLoading}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@ -276,8 +293,6 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{requestAuth}
|
||||
</KeyboardAvoidingView>
|
||||
)
|
||||
}
|
@ -5,6 +5,7 @@ import CustomText from '@components/Text'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { isDevelopment } from '@utils/checkEnvironment'
|
||||
import { useAppsQuery } from '@utils/queryHooks/apps'
|
||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice'
|
||||
import {
|
||||
@ -30,7 +31,16 @@ import { useSelector } from 'react-redux'
|
||||
const TabMePush: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const instance = useSelector(getInstance)
|
||||
const expoToken = useSelector(getExpoToken)
|
||||
|
||||
const [serverKeyAvailable, setServerKeyAvailable] = useState<boolean>()
|
||||
useAppsQuery({
|
||||
options: {
|
||||
onSuccess: data => setServerKeyAvailable(!!data.vapid_key)
|
||||
}
|
||||
})
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const instancePush = useSelector(getInstancePush)
|
||||
@ -38,7 +48,8 @@ const TabMePush: React.FC = () => {
|
||||
const [pushAvailable, setPushAvailable] = useState<boolean>()
|
||||
const [pushEnabled, setPushEnabled] = useState<boolean>()
|
||||
const [pushCanAskAgain, setPushCanAskAgain] = useState<boolean>()
|
||||
const expoToken = useSelector(getExpoToken)
|
||||
|
||||
useEffect(() => {
|
||||
const checkPush = async () => {
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
@ -54,7 +65,8 @@ const TabMePush: React.FC = () => {
|
||||
break
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
|
||||
if (serverKeyAvailable) {
|
||||
checkPush()
|
||||
|
||||
if (isDevelopment) {
|
||||
@ -62,12 +74,13 @@ const TabMePush: React.FC = () => {
|
||||
} else {
|
||||
setPushAvailable(!!expoToken)
|
||||
}
|
||||
}
|
||||
|
||||
const subscription = AppState.addEventListener('change', checkPush)
|
||||
return () => {
|
||||
subscription.remove()
|
||||
}
|
||||
}, [])
|
||||
}, [serverKeyAvailable])
|
||||
|
||||
const alerts = () =>
|
||||
instancePush?.alerts
|
||||
@ -120,6 +133,8 @@ const TabMePush: React.FC = () => {
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
{!!serverKeyAvailable ? (
|
||||
<>
|
||||
{!!pushAvailable ? (
|
||||
<>
|
||||
{pushEnabled === false ? (
|
||||
@ -163,7 +178,9 @@ const TabMePush: React.FC = () => {
|
||||
loading={instancePush?.decode}
|
||||
switchDisabled={!pushEnabled || !instancePush?.global}
|
||||
switchValue={instancePush?.decode}
|
||||
switchOnValueChange={() => dispatch(updateInstancePushDecode(!instancePush?.decode))}
|
||||
switchOnValueChange={() =>
|
||||
dispatch(updateInstancePushDecode(!instancePush?.decode))
|
||||
}
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('me.push.howitworks')}
|
||||
@ -201,6 +218,39 @@ const TabMePush: React.FC = () => {
|
||||
</CustomText>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: '100%',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
>
|
||||
<Icon name='Frown' size={StyleConstants.Font.Size.L} color={colors.primaryDefault} />
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
style={{
|
||||
color: colors.primaryDefault,
|
||||
textAlign: 'center',
|
||||
marginTop: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
{t('me.push.missingServerKey.message')}
|
||||
</CustomText>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
color: colors.primaryDefault,
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
{t('me.push.missingServerKey.description')}
|
||||
</CustomText>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
@ -1,18 +1,41 @@
|
||||
import apiGeneral from '@api/general'
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from 'react-query'
|
||||
import {
|
||||
QueryFunctionContext,
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
UseQueryOptions
|
||||
} from 'react-query'
|
||||
|
||||
export type QueryKeyApps = ['Apps', { domain?: string }]
|
||||
export type QueryKeyApps = ['Apps']
|
||||
|
||||
const queryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyApps>) => {
|
||||
const redirectUri = AuthSession.makeRedirectUri({
|
||||
const queryFunctionApps = async ({ queryKey }: QueryFunctionContext<QueryKeyApps>) => {
|
||||
const res = await apiInstance<Mastodon.Apps>({
|
||||
method: 'get',
|
||||
url: 'apps/verify_credentials'
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useAppsQuery = (
|
||||
params: {
|
||||
options?: UseQueryOptions<Mastodon.Apps, AxiosError>
|
||||
} | void
|
||||
) => {
|
||||
const queryKey: QueryKeyApps = ['Apps']
|
||||
return useQuery(queryKey, queryFunctionApps, params?.options)
|
||||
}
|
||||
|
||||
type MutationVarsApps = { domain: string }
|
||||
|
||||
export const redirectUri = AuthSession.makeRedirectUri({
|
||||
native: 'tooot://instance-auth',
|
||||
useProxy: false
|
||||
})
|
||||
|
||||
const { domain } = queryKey[1]
|
||||
|
||||
const mutationFunctionApps = async ({ domain }: MutationVarsApps) => {
|
||||
const formData = new FormData()
|
||||
formData.append('client_name', 'tooot')
|
||||
formData.append('website', 'https://tooot.app')
|
||||
@ -21,20 +44,16 @@ const queryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyApps>) => {
|
||||
|
||||
return apiGeneral<Mastodon.Apps>({
|
||||
method: 'post',
|
||||
domain: domain || '',
|
||||
domain: domain,
|
||||
url: `api/v1/apps`,
|
||||
body: formData
|
||||
}).then(res => res.body)
|
||||
}
|
||||
|
||||
const useAppsQuery = ({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKeyApps[1] & {
|
||||
options?: UseQueryOptions<Mastodon.Apps, AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKeyApps = ['Apps', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
const useAppsMutation = (
|
||||
options: UseMutationOptions<Mastodon.Apps, AxiosError, MutationVarsApps>
|
||||
) => {
|
||||
return useMutation(mutationFunctionApps, options)
|
||||
}
|
||||
|
||||
export { useAppsQuery }
|
||||
export { useAppsQuery, useAppsMutation }
|
||||
|
Loading…
Reference in New Issue
Block a user