mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Ready for push feature
This commit is contained in:
33
src/utils/migrations/instances/v3.ts
Normal file
33
src/utils/migrations/instances/v3.ts
Normal file
@ -0,0 +1,33 @@
|
||||
type InstanceLocal = {
|
||||
appData: {
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
}
|
||||
url: string
|
||||
token: string
|
||||
uri: Mastodon.Instance['uri']
|
||||
urls: Mastodon.Instance['urls']
|
||||
max_toot_chars: number
|
||||
account: {
|
||||
id: Mastodon.Account['id']
|
||||
acct: Mastodon.Account['acct']
|
||||
avatarStatic: Mastodon.Account['avatar_static']
|
||||
preferences: Mastodon.Preferences
|
||||
}
|
||||
notification: {
|
||||
readTime?: Mastodon.Notification['created_at']
|
||||
latestTime?: Mastodon.Notification['created_at']
|
||||
}
|
||||
drafts: any[]
|
||||
}
|
||||
|
||||
export type InstancesV3 = {
|
||||
local: {
|
||||
activeIndex: number | null
|
||||
instances: InstanceLocal[]
|
||||
}
|
||||
|
||||
remote: {
|
||||
url: string
|
||||
}
|
||||
}
|
91
src/utils/push/useConnect.ts
Normal file
91
src/utils/push/useConnect.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import apiGeneral from '@api/general'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { NavigationContainerRef } from '@react-navigation/native'
|
||||
import { Dispatch } from '@reduxjs/toolkit'
|
||||
import {
|
||||
disableAllPushes,
|
||||
Instance,
|
||||
PUSH_SERVER
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { useEffect } from 'react'
|
||||
import { TFunction } from 'react-i18next'
|
||||
|
||||
export interface Params {
|
||||
navigationRef: React.RefObject<NavigationContainerRef>
|
||||
mode: 'light' | 'dark'
|
||||
t: TFunction<'common'>
|
||||
instances: Instance[]
|
||||
dispatch: Dispatch<any>
|
||||
}
|
||||
|
||||
const pushUseConnect = ({
|
||||
navigationRef,
|
||||
mode,
|
||||
t,
|
||||
instances,
|
||||
dispatch
|
||||
}: Params) => {
|
||||
return useEffect(() => {
|
||||
const connect = async () => {
|
||||
const expoToken = (
|
||||
await Notifications.getExpoPushTokenAsync({
|
||||
experienceId: '@xmflsct/tooot'
|
||||
})
|
||||
).data
|
||||
|
||||
apiGeneral({
|
||||
method: 'post',
|
||||
domain: PUSH_SERVER,
|
||||
url: 'v1/connect',
|
||||
body: {
|
||||
expoToken
|
||||
}
|
||||
}).catch(() => {
|
||||
displayMessage({
|
||||
mode,
|
||||
type: 'error',
|
||||
duration: 'long',
|
||||
message: t('meSettingsPush:error.message'),
|
||||
description: t('meSettingsPush:error.description'),
|
||||
onPress: () => {
|
||||
navigationRef.current?.navigate('Screen-Tabs', {
|
||||
screen: 'Tab-Me',
|
||||
params: {
|
||||
screen: 'Tab-Me-Root'
|
||||
}
|
||||
})
|
||||
navigationRef.current?.navigate('Screen-Tabs', {
|
||||
screen: 'Tab-Me',
|
||||
params: {
|
||||
screen: 'Tab-Me-Settings'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
dispatch(disableAllPushes())
|
||||
|
||||
instances.forEach(instance => {
|
||||
if (instance.push.global.value) {
|
||||
apiGeneral<{}>({
|
||||
method: 'delete',
|
||||
domain: instance.url,
|
||||
url: 'api/v1/push/subscription',
|
||||
headers: {
|
||||
Authorization: `Bearer ${instance.token}`
|
||||
}
|
||||
}).catch(() => console.log('error!!!'))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const pushEnabled = instances.filter(instance => instance.push.global.value)
|
||||
if (pushEnabled.length) {
|
||||
connect()
|
||||
}
|
||||
}, [instances])
|
||||
}
|
||||
|
||||
export default pushUseConnect
|
35
src/utils/push/useNavigate.ts
Normal file
35
src/utils/push/useNavigate.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import { NavigationContainerRef } from '@react-navigation/native'
|
||||
|
||||
const pushUseNavigate = (
|
||||
navigationRef: React.RefObject<NavigationContainerRef>,
|
||||
id?: Mastodon.Notification['id']
|
||||
) => {
|
||||
navigationRef.current?.navigate('Screen-Tabs', {
|
||||
screen: 'Tab-Notifications',
|
||||
params: {
|
||||
screen: 'Tab-Notifications-Root'
|
||||
}
|
||||
})
|
||||
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
|
||||
apiInstance<Mastodon.Notification>({
|
||||
method: 'get',
|
||||
url: `notifications/${id}`
|
||||
}).then(({ body }) => {
|
||||
if (body.status) {
|
||||
navigationRef.current?.navigate('Screen-Tabs', {
|
||||
screen: 'Tab-Notifications',
|
||||
params: {
|
||||
screen: 'Tab-Shared-Toot',
|
||||
params: { toot: body.status }
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default pushUseNavigate
|
58
src/utils/push/useReceive.ts
Normal file
58
src/utils/push/useReceive.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { NavigationContainerRef } from '@react-navigation/native'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { Instance, updateInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { findIndex } from 'lodash'
|
||||
import { useEffect } from 'react'
|
||||
import { QueryClient } from 'react-query'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import pushUseNavigate from './useNavigate'
|
||||
|
||||
export interface Params {
|
||||
navigationRef: React.RefObject<NavigationContainerRef>
|
||||
queryClient: QueryClient
|
||||
instances: Instance[]
|
||||
}
|
||||
|
||||
const pushUseReceive = ({ navigationRef, queryClient, instances }: Params) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return useEffect(() => {
|
||||
const subscription = Notifications.addNotificationReceivedListener(
|
||||
notification => {
|
||||
const queryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{ page: 'Notifications' }
|
||||
]
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
const payloadData = notification.request.content.data as {
|
||||
notification_id?: string
|
||||
instanceUrl: string
|
||||
accountId: string
|
||||
}
|
||||
|
||||
const notificationIndex = findIndex(
|
||||
instances,
|
||||
instance =>
|
||||
instance.url === payloadData.instanceUrl &&
|
||||
instance.account.id === payloadData.accountId
|
||||
)
|
||||
displayMessage({
|
||||
duration: 'long',
|
||||
message: notification.request.content.title!,
|
||||
description: notification.request.content.body!,
|
||||
onPress: () => {
|
||||
if (notificationIndex !== -1) {
|
||||
dispatch(updateInstanceActive(instances[notificationIndex]))
|
||||
}
|
||||
pushUseNavigate(navigationRef, payloadData.notification_id)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
return () => subscription.remove()
|
||||
}, [instances])
|
||||
}
|
||||
|
||||
export default pushUseReceive
|
54
src/utils/push/useRespond.ts
Normal file
54
src/utils/push/useRespond.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { NavigationContainerRef } from '@react-navigation/native'
|
||||
import { Dispatch } from '@reduxjs/toolkit'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { Instance, updateInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { findIndex } from 'lodash'
|
||||
import { useEffect } from 'react'
|
||||
import { QueryClient } from 'react-query'
|
||||
import pushUseNavigate from './useNavigate'
|
||||
|
||||
export interface Params {
|
||||
navigationRef: React.RefObject<NavigationContainerRef>
|
||||
queryClient: QueryClient
|
||||
instances: Instance[]
|
||||
dispatch: Dispatch<any>
|
||||
}
|
||||
|
||||
const pushUseRespond = ({
|
||||
navigationRef,
|
||||
queryClient,
|
||||
instances,
|
||||
dispatch
|
||||
}: Params) => {
|
||||
return useEffect(() => {
|
||||
const subscription = Notifications.addNotificationResponseReceivedListener(
|
||||
({ notification }) => {
|
||||
const queryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
{ page: 'Notifications' }
|
||||
]
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
const payloadData = notification.request.content.data as {
|
||||
notification_id?: string
|
||||
instanceUrl: string
|
||||
accountId: string
|
||||
}
|
||||
|
||||
const notificationIndex = findIndex(
|
||||
instances,
|
||||
instance =>
|
||||
instance.url === payloadData.instanceUrl &&
|
||||
instance.account.id === payloadData.accountId
|
||||
)
|
||||
if (notificationIndex !== -1) {
|
||||
dispatch(updateInstanceActive(instances[notificationIndex]))
|
||||
}
|
||||
pushUseNavigate(navigationRef, payloadData.notification_id)
|
||||
}
|
||||
)
|
||||
return () => subscription.remove()
|
||||
}, [instances])
|
||||
}
|
||||
|
||||
export default pushUseRespond
|
@ -1,38 +0,0 @@
|
||||
import apiGeneral from '@api/general'
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import { RootState } from '@root/store'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { TFunction } from 'react-i18next'
|
||||
import { PUSH_SERVER } from '../instancesSlice'
|
||||
|
||||
export const connectInstancesPush = createAsyncThunk(
|
||||
'instances/connectPush',
|
||||
async (
|
||||
{ mode, t }: { mode: 'light' | 'dark'; t: TFunction<'common'> },
|
||||
{ getState }
|
||||
): Promise<any> => {
|
||||
const state = getState() as RootState
|
||||
const pushEnabled = state.instances.instances.filter(
|
||||
instance => instance.push.global.value
|
||||
)
|
||||
|
||||
if (pushEnabled.length) {
|
||||
const expoToken = (
|
||||
await Notifications.getExpoPushTokenAsync({
|
||||
experienceId: '@xmflsct/tooot'
|
||||
})
|
||||
).data
|
||||
|
||||
return apiGeneral({
|
||||
method: 'post',
|
||||
domain: PUSH_SERVER,
|
||||
url: 'v1/connect',
|
||||
body: {
|
||||
expoToken
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
)
|
@ -1,11 +1,9 @@
|
||||
import analytics from '@components/analytics'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { RootState } from '@root/store'
|
||||
import { ComposeStateDraft } from '@screens/Compose/utils/types'
|
||||
import { findIndex } from 'lodash'
|
||||
import addInstance from './instances/add'
|
||||
import { connectInstancesPush } from './instances/connectPush'
|
||||
import removeInstance from './instances/remove'
|
||||
import { updateAccountPreferences } from './instances/updateAccountPreferences'
|
||||
import { updateInstancePush } from './instances/updatePush'
|
||||
@ -150,6 +148,13 @@ const instancesSlice = createSlice({
|
||||
instances[activeIndex].drafts = instances[activeIndex].drafts?.filter(
|
||||
draft => draft.timestamp !== action.payload
|
||||
)
|
||||
},
|
||||
disableAllPushes: ({ instances }) => {
|
||||
instances = instances.map(instance => {
|
||||
let newInstance = instance
|
||||
newInstance.push.global.value = false
|
||||
return newInstance
|
||||
})
|
||||
}
|
||||
},
|
||||
extraReducers: builder => {
|
||||
@ -266,22 +271,6 @@ const instancesSlice = createSlice({
|
||||
action.meta.arg.changed
|
||||
].loading = true
|
||||
})
|
||||
|
||||
// If Expo token does not exist on the server, disable all existing ones
|
||||
.addCase(connectInstancesPush.rejected, (state, action) => {
|
||||
state.instances = state.instances.map(instance => {
|
||||
let newInstance = instance
|
||||
newInstance.push.global.value = false
|
||||
displayMessage({
|
||||
mode: action.meta.arg.mode,
|
||||
type: 'error',
|
||||
autoHide: false,
|
||||
message: action.meta.arg.t('meSettingsPush:error.message'),
|
||||
description: action.meta.arg.t('meSettingsPush:error.description')
|
||||
})
|
||||
return newInstance
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@ -337,7 +326,8 @@ export const {
|
||||
updateInstanceActive,
|
||||
updateInstanceAccount,
|
||||
updateInstanceDraft,
|
||||
removeInstanceDraft
|
||||
removeInstanceDraft,
|
||||
disableAllPushes
|
||||
} = instancesSlice.actions
|
||||
|
||||
export default instancesSlice.reducer
|
||||
|
Reference in New Issue
Block a user