tooot/src/utils/slices/instancesSlice.ts

296 lines
8.1 KiB
TypeScript
Raw Normal View History

2021-01-07 19:13:09 +01:00
import client from '@api/client'
import analytics from '@components/analytics'
2020-12-24 10:28:51 +01:00
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
2020-12-13 14:04:25 +01:00
import { RootState } from '@root/store'
2021-01-07 19:13:09 +01:00
import * as AuthSession from 'expo-auth-session'
2021-01-17 22:37:05 +01:00
import * as Localization from 'expo-localization'
2021-01-07 19:13:09 +01:00
export type InstanceLocal = {
appData: {
clientId: string
clientSecret: string
}
url: string
token: string
2021-01-17 22:37:05 +01:00
uri: Mastodon.Instance['uri']
2021-01-07 19:13:09 +01:00
account: {
id: Mastodon.Account['id']
2021-01-23 02:41:50 +01:00
acct: Mastodon.Account['acct']
avatarStatic: Mastodon.Account['avatar_static']
2021-01-07 19:13:09 +01:00
preferences: Mastodon.Preferences
}
notification: {
latestTime?: Mastodon.Notification['created_at']
}
}
2020-11-21 13:19:05 +01:00
export type InstancesState = {
local: {
2021-01-07 19:13:09 +01:00
activeIndex: number | null
instances: InstanceLocal[]
}
2021-01-07 19:13:09 +01:00
remote: {
url: string
}
}
2021-01-07 19:13:09 +01:00
export const localUpdateAccountPreferences = createAsyncThunk(
'instances/localUpdateAccountPreferences',
async (): Promise<Mastodon.Preferences> => {
const preferences = await client<Mastodon.Preferences>({
method: 'get',
instance: 'local',
url: `preferences`
})
2021-01-07 19:13:09 +01:00
return Promise.resolve(preferences)
}
)
2021-01-07 19:13:09 +01:00
export const localAddInstance = createAsyncThunk(
'instances/localAddInstance',
async ({
url,
2021-01-07 19:13:09 +01:00
token,
2021-01-17 22:37:05 +01:00
uri,
2021-01-07 19:13:09 +01:00
appData
}: {
2021-01-07 19:13:09 +01:00
url: InstanceLocal['url']
token: InstanceLocal['token']
2021-01-17 22:37:05 +01:00
uri: Mastodon.Instance['uri']
2021-01-07 19:13:09 +01:00
appData: InstanceLocal['appData']
2021-01-10 02:12:14 +01:00
}): Promise<{ type: 'add' | 'overwrite'; data: InstanceLocal }> => {
const { store } = require('@root/store')
const instanceLocal: InstancesState['local'] = store.getState().instances
.local
2021-01-07 19:13:09 +01:00
2021-01-23 02:41:50 +01:00
const { id, acct, avatar_static } = await client<Mastodon.Account>({
method: 'get',
instance: 'remote',
instanceDomain: url,
url: `accounts/verify_credentials`,
headers: { Authorization: `Bearer ${token}` }
})
2021-01-10 02:12:14 +01:00
let type: 'add' | 'overwrite'
if (
instanceLocal.instances.filter(instance => {
if (instance) {
if (instance.url === url && instance.account.id === id) {
return true
} else {
return false
}
} else {
return false
}
}).length
) {
type = 'overwrite'
} else {
type = 'add'
}
2021-01-07 19:13:09 +01:00
const preferences = await client<Mastodon.Preferences>({
method: 'get',
instance: 'remote',
instanceDomain: url,
url: `preferences`,
headers: { Authorization: `Bearer ${token}` }
})
2021-01-07 19:13:09 +01:00
return Promise.resolve({
2021-01-10 02:12:14 +01:00
type,
data: {
appData,
url,
token,
2021-01-17 22:37:05 +01:00
uri,
2021-01-10 02:12:14 +01:00
account: {
id,
2021-01-23 02:41:50 +01:00
acct,
avatarStatic: avatar_static,
2021-01-10 02:12:14 +01:00
preferences
},
notification: {
2021-02-01 02:16:53 +01:00
latestTime: undefined
2021-01-10 02:12:14 +01:00
}
2021-01-07 19:13:09 +01:00
}
})
}
)
export const localRemoveInstance = createAsyncThunk(
'instances/localRemoveInstance',
async (index?: InstancesState['local']['activeIndex']): Promise<number> => {
2021-01-10 02:12:14 +01:00
const { store } = require('@root/store')
const instanceLocal: InstancesState['local'] = store.getState().instances
.local
2021-01-07 19:13:09 +01:00
if (index) {
return Promise.resolve(index)
} else {
2021-01-10 02:12:14 +01:00
if (instanceLocal.activeIndex !== null) {
const currentInstance =
instanceLocal.instances[instanceLocal.activeIndex]
let revoked = undefined
try {
revoked = await AuthSession.revokeAsync(
{
clientId: currentInstance.appData.clientId,
clientSecret: currentInstance.appData.clientSecret,
token: currentInstance.token,
scopes: ['read', 'write', 'follow', 'push']
},
{
revocationEndpoint: `https://${currentInstance.url}/oauth/revoke`
}
)
} catch {}
2021-01-07 19:13:09 +01:00
if (!revoked) {
console.warn('Revoking error')
}
2021-01-10 02:12:14 +01:00
return Promise.resolve(instanceLocal.activeIndex)
2021-01-07 19:13:09 +01:00
} else {
throw new Error('Active index invalid, cannot remove instance')
}
2021-01-07 19:13:09 +01:00
}
}
)
2021-01-07 19:13:09 +01:00
export const instancesInitialState: InstancesState = {
local: {
activeIndex: null,
instances: []
},
remote: {
2021-01-17 22:37:05 +01:00
url: Localization.locale.includes('zh') ? 'm.cmx.im' : 'mastodon.social'
2021-01-07 19:13:09 +01:00
}
}
const instancesSlice = createSlice({
name: 'instances',
2021-01-07 19:13:09 +01:00
initialState: instancesInitialState,
2020-12-22 00:10:55 +01:00
reducers: {
2021-01-07 19:13:09 +01:00
localUpdateActiveIndex: (
state,
action: PayloadAction<NonNullable<InstancesState['local']['activeIndex']>>
) => {
if (action.payload < state.local.instances.length) {
state.local.activeIndex = action.payload
} else {
throw new Error('Set index cannot be found')
}
2020-12-24 10:28:51 +01:00
},
2021-01-23 02:41:50 +01:00
localUpdateAccount: (
state,
action: PayloadAction<
Pick<InstanceLocal['account'], 'acct' & 'avatarStatic'>
>
) => {
if (state.local.activeIndex !== null) {
state.local.instances[state.local.activeIndex].account = {
...state.local.instances[state.local.activeIndex].account,
...action.payload
}
}
},
2021-01-07 19:13:09 +01:00
localUpdateNotification: (
2020-12-24 10:28:51 +01:00
state,
2021-01-07 19:13:09 +01:00
action: PayloadAction<Partial<InstanceLocal['notification']>>
2020-12-24 10:28:51 +01:00
) => {
2021-02-01 02:16:53 +01:00
state.local.instances[state.local.activeIndex!].notification =
action.payload
2021-01-07 19:13:09 +01:00
},
remoteUpdate: (
state,
action: PayloadAction<InstancesState['remote']['url']>
) => {
state.remote.url = action.payload
2020-12-22 00:10:55 +01:00
}
},
extraReducers: builder => {
builder
2021-01-07 19:13:09 +01:00
.addCase(localAddInstance.fulfilled, (state, action) => {
2021-01-10 02:12:14 +01:00
switch (action.payload.type) {
case 'add':
state.local.instances.push(action.payload.data)
state.local.activeIndex = state.local.instances.length - 1
break
case 'overwrite':
state.local.instances = state.local.instances.map(instance => {
if (
instance.url === action.payload.data.url &&
instance.account.id === action.payload.data.account.id
) {
return action.payload.data
} else {
return instance
}
})
}
2021-01-07 19:13:09 +01:00
analytics('login')
})
2021-01-07 19:13:09 +01:00
.addCase(localAddInstance.rejected, (state, action) => {
console.error(state.local)
console.error(action.error)
})
.addCase(localRemoveInstance.fulfilled, (state, action) => {
state.local.instances.splice(action.payload, 1)
state.local.activeIndex = state.local.instances.length
? state.local.instances.length - 1
: null
analytics('logout')
})
.addCase(localRemoveInstance.rejected, (state, action) => {
console.error(state.local)
console.error(action.error)
})
.addCase(localUpdateAccountPreferences.fulfilled, (state, action) => {
state.local.instances[state.local.activeIndex!].account.preferences =
action.payload
})
.addCase(localUpdateAccountPreferences.rejected, (_, action) => {
console.error(action.error)
})
}
})
2021-01-07 19:13:09 +01:00
export const getLocalActiveIndex = ({ instances: { local } }: RootState) =>
local.activeIndex
export const getLocalInstances = ({ instances: { local } }: RootState) =>
local.instances
export const getLocalUrl = ({ instances: { local } }: RootState) =>
2021-01-20 00:39:39 +01:00
local.activeIndex !== null
? local.instances[local.activeIndex].url
: undefined
export const getLocalUri = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
2021-01-22 01:34:20 +01:00
? local.instances[local.activeIndex].uri
2021-01-20 00:39:39 +01:00
: undefined
2021-01-07 19:13:09 +01:00
export const getLocalAccount = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].account
: undefined
export const getLocalNotification = ({ instances: { local } }: RootState) =>
local.activeIndex !== null
? local.instances[local.activeIndex].notification
: undefined
export const getRemoteUrl = ({ instances: { remote } }: RootState) => remote.url
export const {
localUpdateActiveIndex,
2021-01-23 02:41:50 +01:00
localUpdateAccount,
2021-01-07 19:13:09 +01:00
localUpdateNotification,
remoteUpdate
} = instancesSlice.actions
2020-12-22 00:10:55 +01:00
export default instancesSlice.reducer