1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Rewrite timeline logic

This commit is contained in:
Zhiyuan Zheng
2021-02-27 16:33:54 +01:00
parent 45681fc1f5
commit f3fa6bc662
67 changed files with 1980 additions and 1395 deletions

View File

@ -75,8 +75,16 @@ const addInstance = createAsyncThunk(
latestTime: undefined
},
push: {
loading: false,
enabled: false
global: { loading: false, value: false },
decode: { loading: false, value: false },
alerts: {
follow: { loading: false, value: true },
favourite: { loading: false, value: true },
reblog: { loading: false, value: true },
mention: { loading: false, value: true },
poll: { loading: false, value: true }
},
keys: undefined
},
drafts: []
}

View File

@ -1,21 +0,0 @@
import apiGeneral from '@api/general'
import * as Notifications from 'expo-notifications'
const serverUnregister = async () => {
const deviceToken = (await Notifications.getDevicePushTokenAsync()).data
return apiGeneral<{ endpoint: string; publicKey: string; auth: string }>({
method: 'post',
domain: 'testpush.home.xmflsct.com',
url: 'unregister',
body: { deviceToken }
})
}
const pushDisable = async () => {
await serverUnregister()
return false
}
export default pushDisable

View File

@ -1,61 +0,0 @@
import apiGeneral from '@api/general'
import apiInstance from '@api/instance'
import * as Notifications from 'expo-notifications'
import { Platform } from 'react-native'
const serverRegister = async () => {
const deviceToken = (await Notifications.getDevicePushTokenAsync()).data
const expoToken = (
await Notifications.getExpoPushTokenAsync({
experienceId: '@xmflsct/tooot'
})
).data
return apiGeneral<{ endpoint: string; publicKey: string; auth: string }>({
method: 'post',
domain: 'testpush.home.xmflsct.com',
url: 'register',
body: { deviceToken, expoToken }
})
}
const pushEnable = async (): Promise<Mastodon.PushSubscription> => {
const { status: existingStatus } = await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
finalStatus = status
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!')
return Promise.reject()
}
const serverRes = (await serverRegister()).body
const formData = new FormData()
formData.append(
'subscription[endpoint]',
'https://testpush.home.xmflsct.com/test1'
)
formData.append('subscription[keys][p256dh]', serverRes.publicKey)
formData.append('subscription[keys][auth]', serverRes.auth)
const res = await apiInstance<Mastodon.PushSubscription>({
method: 'post',
url: 'push/subscription',
body: formData
})
return res.body
// if (Platform.OS === 'android') {
// Notifications.setNotificationChannelAsync('default', {
// name: 'default',
// importance: Notifications.AndroidImportance.MAX,
// vibrationPattern: [0, 250, 250, 250],
// lightColor: '#FF231F7C'
// })
// }
}
export default pushEnable

View File

@ -0,0 +1,127 @@
import apiGeneral from '@api/general'
import apiInstance from '@api/instance'
import { RootState } from '@root/store'
import {
getInstance,
Instance,
PUSH_SERVER
} from '@utils/slices/instancesSlice'
import * as Notifications from 'expo-notifications'
import { Platform } from 'react-native'
const register1 = async ({
expoToken,
instanceUrl,
accountId,
accountFull
}: {
expoToken: string
instanceUrl: string
accountId: Mastodon.Account['id']
accountFull: string
}) => {
return apiGeneral<{
endpoint: string
keys: { public: string; private: string; auth: string }
}>({
method: 'post',
domain: PUSH_SERVER,
url: 'v1/register1',
body: { expoToken, instanceUrl, accountId, accountFull }
})
}
const register2 = async ({
expoToken,
serverKey,
instanceUrl,
accountId,
removeKeys
}: {
expoToken: string
serverKey: Mastodon.PushSubscription['server_key']
instanceUrl: string
accountId: Mastodon.Account['id']
removeKeys: boolean
}) => {
return apiGeneral({
method: 'post',
domain: PUSH_SERVER,
url: 'v1/register2',
body: { expoToken, instanceUrl, accountId, serverKey, removeKeys }
})
}
const pushRegister = async (
state: RootState,
expoToken: string
): Promise<Instance['push']['keys']> => {
const instance = getInstance(state)
const instanceUrl = instance?.url
const instanceUri = instance?.uri
const instanceAccount = instance?.account
const instancePush = instance?.push
if (!instanceUrl || !instanceUri || !instanceAccount || !instancePush) {
return Promise.reject()
}
const { status: existingStatus } = await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
finalStatus = status
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!')
return Promise.reject()
}
const accountId = instanceAccount.id
const accountFull = `@${instanceAccount.acct}@${instanceUri}`
const serverRes = await register1({
expoToken,
instanceUrl,
accountId,
accountFull
})
console.log('endpoint', serverRes.body.endpoint)
console.log('token', instance?.token)
const alerts = instancePush.alerts
const formData = new FormData()
formData.append('subscription[endpoint]', serverRes.body.endpoint)
formData.append('subscription[keys][p256dh]', serverRes.body.keys.public)
formData.append('subscription[keys][auth]', serverRes.body.keys.auth)
Object.keys(alerts).map(key =>
// @ts-ignore
formData.append(`data[alerts][${key}]`, alerts[key].value.toString())
)
const res = await apiInstance<Mastodon.PushSubscription>({
method: 'post',
url: 'push/subscription',
body: formData
})
await register2({
expoToken,
serverKey: res.body.server_key,
instanceUrl,
accountId,
removeKeys: instancePush.decode.value === false
})
return Promise.resolve(serverRes.body.keys)
// if (Platform.OS === 'android') {
// Notifications.setNotificationChannelAsync('default', {
// name: 'default',
// importance: Notifications.AndroidImportance.MAX,
// vibrationPattern: [0, 250, 250, 250],
// lightColor: '#FF231F7C'
// })
// }
}
export default pushRegister

View File

@ -0,0 +1,32 @@
import apiGeneral from '@api/general'
import apiInstance from '@api/instance'
import { RootState } from '@root/store'
import { getInstance, PUSH_SERVER } from '@utils/slices/instancesSlice'
const pushUnregister = async (state: RootState, expoToken: string) => {
const instance = getInstance(state)
if (!instance?.url || !instance.account.id) {
return Promise.reject()
}
await apiInstance<{}>({
method: 'delete',
url: 'push/subscription'
})
await apiGeneral<{ endpoint: string; publicKey: string; auth: string }>({
method: 'post',
domain: PUSH_SERVER,
url: 'v1/unregister',
body: {
expoToken,
instanceUrl: instance.url,
accountId: instance.account.id
}
})
return
}
export default pushUnregister

View File

@ -1,17 +1,27 @@
import { createAsyncThunk } from '@reduxjs/toolkit'
import { RootState } from '@root/store'
import * as Notifications from 'expo-notifications'
import { Instance } from '../instancesSlice'
import pushDisable from './push/disable'
import pushEnable from './push/enable'
import pushRegister from './push/register'
import pushUnregister from './push/unregister'
export const updatePush = createAsyncThunk(
export const updateInstancePush = createAsyncThunk(
'instances/updatePush',
async (
enable: boolean
): Promise<Instance['push']['subscription'] | boolean> => {
if (enable) {
return pushEnable()
disable: boolean,
{ getState }
): Promise<Instance['push']['keys'] | undefined> => {
const state = getState() as RootState
const expoToken = (
await Notifications.getExpoPushTokenAsync({
experienceId: '@xmflsct/tooot'
})
).data
if (disable) {
return await pushRegister(state, expoToken)
} else {
return pushDisable()
return await pushUnregister(state, expoToken)
}
}
)

View File

@ -0,0 +1,27 @@
import apiInstance from '@api/instance'
import { createAsyncThunk } from '@reduxjs/toolkit'
import { Instance } from '../instancesSlice'
export const updateInstancePushAlert = createAsyncThunk(
'instances/updatePushAlert',
async ({
alerts
}: {
changed: keyof Instance['push']['alerts']
alerts: Instance['push']['alerts']
}): Promise<Instance['push']['alerts']> => {
const formData = new FormData()
Object.keys(alerts).map(alert =>
// @ts-ignore
formData.append(`data[alerts][${alert}]`, alerts[alert].value.toString())
)
await apiInstance<Mastodon.PushSubscription>({
method: 'put',
url: 'push/subscription',
body: formData
})
return Promise.resolve(alerts)
}
)

View File

@ -0,0 +1,39 @@
import apiGeneral from '@api/general'
import { createAsyncThunk } from '@reduxjs/toolkit'
import { RootState } from '@root/store'
import * as Notifications from 'expo-notifications'
import { getInstance, Instance, PUSH_SERVER } from '../instancesSlice'
export const updateInstancePushDecode = createAsyncThunk(
'instances/updatePushDecode',
async (
disalbe: boolean,
{ getState }
): Promise<Instance['push']['decode']['value']> => {
const state = getState() as RootState
const instance = getInstance(state)
if (!instance?.url || !instance.account.id || !instance.push.keys) {
return Promise.reject()
}
const expoToken = (
await Notifications.getExpoPushTokenAsync({
experienceId: '@xmflsct/tooot'
})
).data
await apiGeneral({
method: 'post',
domain: PUSH_SERVER,
url: 'v1/update-decode',
body: {
expoToken,
instanceUrl: instance.url,
accountId: instance.account.id,
...(disalbe && { keys: instance.push.keys })
}
})
return Promise.resolve(disalbe)
}
)

View File

@ -6,7 +6,11 @@ import { findIndex } from 'lodash'
import addInstance from './instances/add'
import removeInstance from './instances/remove'
import { updateAccountPreferences } from './instances/updateAccountPreferences'
import { updatePush } from './instances/updatePush'
import { updateInstancePush } from './instances/updatePush'
import { updateInstancePushAlert } from './instances/updatePushAlert'
import { updateInstancePushDecode } from './instances/updatePushDecode'
export const PUSH_SERVER = __DEV__ ? 'testpush.tooot.app' : 'push.tooot.app'
export type Instance = {
active: boolean
@ -29,11 +33,65 @@ export type Instance = {
readTime?: Mastodon.Notification['created_at']
latestTime?: Mastodon.Notification['created_at']
}
push: {
loading: boolean
enabled: boolean
subscription?: Mastodon.PushSubscription
}
push:
| {
global: { loading: boolean; value: true }
decode: { loading: boolean; value: boolean }
alerts: {
follow: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['follow']
}
favourite: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['favourite']
}
reblog: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['reblog']
}
mention: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['mention']
}
poll: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['poll']
}
}
keys: {
auth: string
public: string
private: string
}
}
| {
global: { loading: boolean; value: false }
decode: { loading: boolean; value: boolean }
alerts: {
follow: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['follow']
}
favourite: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['favourite']
}
reblog: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['reblog']
}
mention: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['mention']
}
poll: {
loading: boolean
value: Mastodon.PushSubscription['alerts']['poll']
}
}
keys: undefined
}
drafts: ComposeStateDraft[]
}
@ -119,7 +177,6 @@ const instancesSlice = createSlice({
state.instances.push(action.payload.data)
break
case 'overwrite':
console.log('overwriting')
state.instances = state.instances.map(instance => {
if (
instance.url === action.payload.data.url &&
@ -152,6 +209,7 @@ const instancesSlice = createSlice({
console.error(action.error)
})
// Update Instance Account Preferences
.addCase(updateAccountPreferences.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].account.preferences = action.payload
@ -160,14 +218,56 @@ const instancesSlice = createSlice({
console.error(action.error)
})
.addCase(updatePush.fulfilled, (state, action) => {
// Update Instance Push Global
.addCase(updateInstancePush.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
if (typeof action.payload === 'boolean') {
state.instances[activeIndex].push.enabled = action.payload
} else {
state.instances[activeIndex].push.enabled = true
state.instances[activeIndex].push.subscription = action.payload
}
state.instances[activeIndex].push.global.loading = false
state.instances[activeIndex].push.global.value = action.meta.arg
state.instances[activeIndex].push.keys = action.payload
})
.addCase(updateInstancePush.rejected, state => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.global.loading = false
})
.addCase(updateInstancePush.pending, state => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.global.loading = true
})
// Update Instance Push Decode
.addCase(updateInstancePushDecode.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.decode.loading = false
state.instances[activeIndex].push.decode.value = action.payload
})
.addCase(updateInstancePushDecode.rejected, state => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.decode.loading = false
})
.addCase(updateInstancePushDecode.pending, state => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.decode.loading = true
})
// Update Instance Push Individual Alert
.addCase(updateInstancePushAlert.fulfilled, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.alerts[
action.meta.arg.changed
].loading = false
state.instances[activeIndex].push.alerts = action.payload
})
.addCase(updateInstancePushAlert.rejected, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.alerts[
action.meta.arg.changed
].loading = false
})
.addCase(updateInstancePushAlert.pending, (state, action) => {
const activeIndex = findInstanceActive(state.instances)
state.instances[activeIndex].push.alerts[
action.meta.arg.changed
].loading = true
})
}
})
@ -217,6 +317,11 @@ export const getInstanceNotification = ({
return instanceActive !== -1 ? instances[instanceActive].notification : null
}
export const getInstancePush = ({ instances: { instances } }: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].push : null
}
export const getInstanceDrafts = ({ instances: { instances } }: RootState) => {
const instanceActive = findInstanceActive(instances)
return instanceActive !== -1 ? instances[instanceActive].drafts : null