pleroma-fe/src/modules/api.js
flisk 999c38594e fix realtime updates in 'following' replies filter
i'm not sure how this code was supposed to work, but the way it was
written would only add statuses to the timeline if they were in reply to
someone the user is following and erroneously filter out posts that
aren't replies.
2023-02-24 00:23:53 +01:00

336 lines
12 KiB
JavaScript

import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { WSConnectionStatus } from '../services/api/api.service.js'
import { map } from 'lodash'
const retryTimeout = (multiplier) => 1000 * multiplier
const isVisible = (store, message, visibility) => {
if (visibility == 'all') {
return true
}
if (visibility == 'following') {
if (message.in_reply_to_user_id === null) {
return true
} else {
return store.getters.relationship(message.in_reply_to_user_id).following
}
}
if (visibility == 'self') {
return message.in_reply_to_user_id === store.rootState.users.currentUser.id
}
return false
}
const api = {
state: {
retryMultiplier: 1,
backendInteractor: backendInteractorService(),
fetchers: {},
socket: null,
mastoUserSocket: null,
mastoUserSocketStatus: null,
followRequests: []
},
mutations: {
setBackendInteractor (state, backendInteractor) {
state.backendInteractor = backendInteractor
},
addFetcher (state, { fetcherName, fetcher }) {
state.fetchers[fetcherName] = fetcher
},
removeFetcher (state, { fetcherName, fetcher }) {
state.fetchers[fetcherName].stop()
delete state.fetchers[fetcherName]
},
setWsToken (state, token) {
state.wsToken = token
},
setSocket (state, socket) {
state.socket = socket
},
setMastoUserSocketStatus (state, value) {
state.mastoUserSocketStatus = value
},
incrementRetryMultiplier (state) {
state.retryMultiplier = Math.max(++state.retryMultiplier, 3)
},
resetRetryMultiplier (state) {
state.retryMultiplier = 1
},
setFollowRequests (state, value) {
state.followRequests = [...value]
},
saveFollowRequests (state, requests) {
state.followRequests = [...state.followRequests, ...requests]
},
saveFollowRequestPagination (state, pagination) {
state.followRequestsPagination = pagination
}
},
actions: {
/**
* Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
*
* @param {Boolean} [initial] - whether this enabling happened at boot time or not
*/
enableMastoSockets (store, initial) {
const { state, dispatch, commit } = store
// Do not initialize unless nonexistent or closed
if (
state.mastoUserSocket &&
![
WebSocket.CLOSED,
WebSocket.CLOSING
].includes(state.mastoUserSocket.getState())
) {
return
}
if (initial) {
commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING_INITIAL)
} else {
commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING)
}
return dispatch('startMastoUserSocket')
},
disableMastoSockets (store) {
const { state, dispatch, commit } = store
if (!state.mastoUserSocket) return
commit('setMastoUserSocketStatus', WSConnectionStatus.DISABLED)
return dispatch('stopMastoUserSocket')
},
// MastoAPI 'User' sockets
startMastoUserSocket (store) {
return new Promise((resolve, reject) => {
try {
const { state, commit, dispatch, rootState } = store
const timelineData = rootState.statuses.timelines.friends
state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
state.mastoUserSocket.addEventListener(
'message',
({ detail: message }) => {
const replyVisibility = rootState.config.replyVisibility
if (!message) return // pings
if (message.event === 'notification') {
dispatch('addNewNotifications', {
notifications: [message.notification],
older: false
})
} else if (message.event === 'update' && isVisible(store, message.status, replyVisibility)) {
dispatch('addNewStatuses', {
statuses: [message.status],
userId: false,
showImmediately: timelineData.visibleStatuses.length === 0,
timeline: 'friends'
})
} else if (message.event === 'status.update' && isVisible(store, message.status, replyVisibility)) {
dispatch('addNewStatuses', {
statuses: [message.status],
userId: false,
showImmediately: message.status.id in timelineData.visibleStatusesObject,
timeline: 'friends'
})
} else if (message.event === 'delete') {
dispatch('deleteStatusById', message.id)
}
}
)
state.mastoUserSocket.addEventListener('open', () => {
// Do not show notification when we just opened up the page
if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) {
dispatch('pushGlobalNotice', {
level: 'success',
messageKey: 'timeline.socket_reconnected',
timeout: 5000
})
}
// Stop polling if we were errored or disabled
if (new Set([
WSConnectionStatus.ERROR,
WSConnectionStatus.DISABLED
]).has(state.mastoUserSocketStatus)) {
dispatch('stopFetchingTimeline', { timeline: 'friends' })
dispatch('stopFetchingNotifications')
}
commit('resetRetryMultiplier')
commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)
})
state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
console.error('Error in MastoAPI websocket:', error)
})
state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
const ignoreCodes = new Set([
1000, // Normal (intended) closure
1001 // Going away
])
const { code } = closeEvent
if (ignoreCodes.has(code)) {
console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
} else {
console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
setTimeout(() => {
dispatch('startMastoUserSocket')
}, retryTimeout(state.retryMultiplier))
commit('incrementRetryMultiplier')
if (state.mastoUserSocketStatus !== WSConnectionStatus.ERROR) {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
dispatch('startFetchingAnnouncements')
dispatch('startFetchingReports')
dispatch('pushGlobalNotice', {
level: 'error',
messageKey: 'timeline.socket_broke',
messageArgs: [code],
timeout: 5000
})
}
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
}
})
resolve()
} catch (e) {
reject(e)
}
})
},
stopMastoUserSocket ({ state, dispatch }) {
dispatch('startFetchingTimeline', { timeline: 'friends' })
dispatch('startFetchingNotifications')
state.mastoUserSocket.close()
},
// Timelines
startFetchingTimeline (store, {
timeline = 'friends',
tag = false,
userId = false,
listId = false
}) {
if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({
timeline, store, userId, listId, tag
})
store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
stopFetchingTimeline (store, timeline) {
const fetcher = store.state.fetchers[timeline]
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: timeline, fetcher })
},
fetchTimeline (store, timeline, { ...rest }) {
store.state.backendInteractor.fetchTimeline({
store,
timeline,
...rest
})
},
startFetchingConfig (store) {
if (store.state.fetchers.config) return
const fetcher = store.state.backendInteractor.startFetchingConfig({ store })
store.commit('addFetcher', { fetcherName: 'config', fetcher })
},
stopFetchingConfig (store) {
const fetcher = store.state.fetchers.config
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'config', fetcher })
},
// Notifications
startFetchingNotifications (store) {
if (store.state.fetchers.notifications) return
const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
},
stopFetchingNotifications (store) {
const fetcher = store.state.fetchers.notifications
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
},
fetchNotifications (store, { ...rest }) {
store.state.backendInteractor.fetchNotifications({
store,
...rest
})
},
removeFollowRequest (store, request) {
let requests = [...store.state.followRequests].filter((it) => it.id !== request.id)
store.commit('setFollowRequests', requests)
},
fetchFollowRequests ({ rootState, commit }) {
const pagination = rootState.api.followRequestsPagination
return rootState.api.backendInteractor.getFollowRequests({ pagination })
.then((requests) => {
if (requests.data.length > 0) {
commit('addNewUsers', requests.data)
commit('saveFollowRequests', requests.data)
commit('saveFollowRequestPagination', requests.pagination)
}
return requests
})
},
// Lists
startFetchingLists (store) {
if (store.state.fetchers['lists']) return
const fetcher = store.state.backendInteractor.startFetchingLists({ store })
store.commit('addFetcher', { fetcherName: 'lists', fetcher })
},
stopFetchingLists (store) {
const fetcher = store.state.fetchers.lists
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'lists', fetcher })
},
// Lists
startFetchingAnnouncements (store) {
if (store.state.fetchers['announcements']) return
const fetcher = store.state.backendInteractor.startFetchingAnnouncements({ store })
store.commit('addFetcher', { fetcherName: 'announcements', fetcher })
},
stopFetchingAnnouncements (store) {
const fetcher = store.state.fetchers.announcements
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'announcements', fetcher })
},
// Reports
startFetchingReports (store) {
if (store.state.fetchers['reports']) return
const fetcher = store.state.backendInteractor.startFetchingReports({ store })
store.commit('addFetcher', { fetcherName: 'reports', fetcher })
},
stopFetchingReports (store) {
const fetcher = store.state.fetchers.reports
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'reports', fetcher })
},
getSupportedTranslationlanguages (store) {
store.state.backendInteractor.getSupportedTranslationlanguages({ store })
.then((data) => {
store.dispatch('setInstanceOption', { name: 'supportedTranslationLanguages', value: data })
})
},
listSettingsProfiles (store) {
store.state.backendInteractor.listSettingsProfiles({ store })
.then((data) => {
store.commit('setInstanceOption', { name: 'settingsProfiles', value: data })
})
},
// Pleroma websocket
setWsToken (store, token) {
store.commit('setWsToken', token)
},
disconnectFromSocket ({ commit, state }) {
state.socket && state.socket.disconnect()
commit('setSocket', null)
}
}
}
export default api