initial streaming re-support
This commit is contained in:
parent
4de092808d
commit
652a9fabb7
11 changed files with 391 additions and 281 deletions
|
|
@ -126,14 +126,14 @@ export const handleMastoWS = (
|
||||||
const { data } = wsEvent
|
const { data } = wsEvent
|
||||||
if (!data) return
|
if (!data) return
|
||||||
const parsedEvent = JSON.parse(data)
|
const parsedEvent = JSON.parse(data)
|
||||||
const { event, payload } = parsedEvent
|
const { event, stream, payload } = parsedEvent
|
||||||
if (
|
if (
|
||||||
MASTODON_STREAMING_EVENTS.has(event) ||
|
MASTODON_STREAMING_EVENTS.has(event) ||
|
||||||
PLEROMA_STREAMING_EVENTS.has(event)
|
PLEROMA_STREAMING_EVENTS.has(event)
|
||||||
) {
|
) {
|
||||||
// MastoBE and PleromaBE both send payload for delete as a PLAIN string
|
// MastoBE and PleromaBE both send payload for delete as a PLAIN string
|
||||||
if (event === 'delete') {
|
if (event === 'delete') {
|
||||||
return { event, id: payload }
|
return { event, stream, id: payload }
|
||||||
}
|
}
|
||||||
const data = payload ? JSON.parse(payload) : null
|
const data = payload ? JSON.parse(payload) : null
|
||||||
if (event === 'pleroma:respond') {
|
if (event === 'pleroma:respond') {
|
||||||
|
|
@ -150,13 +150,13 @@ export const handleMastoWS = (
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
} else if (event === 'update') {
|
} else if (event === 'update') {
|
||||||
return { event, status: parseStatus(data) }
|
return { event, stream, status: parseStatus(data) }
|
||||||
} else if (event === 'status.update') {
|
} else if (event === 'status.update') {
|
||||||
return { event, status: parseStatus(data) }
|
return { event, stream, status: parseStatus(data) }
|
||||||
} else if (event === 'notification') {
|
} else if (event === 'notification') {
|
||||||
return { event, notification: parseNotification(data) }
|
return { event, stream, notification: parseNotification(data) }
|
||||||
} else if (event === 'pleroma:chat_update') {
|
} else if (event === 'pleroma:chat_update') {
|
||||||
return { event, chatUpdate: parseChat(data) }
|
return { event, stream, chatUpdate: parseChat(data) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Unknown event', wsEvent)
|
console.warn('Unknown event', wsEvent)
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ export default (store) => {
|
||||||
path: '/lists/:id',
|
path: '/lists/:id',
|
||||||
component: Timeline,
|
component: Timeline,
|
||||||
props: (route) => ({
|
props: (route) => ({
|
||||||
timelineRef: { name: 'lists', argument: route.params.id },
|
timelineRef: { name: 'list', argument: route.params.id },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const Timeline = {
|
||||||
filteredVisibleStatuses() {
|
filteredVisibleStatuses() {
|
||||||
return [...this.timeline.visibleStatusesIds.keys()]
|
return [...this.timeline.visibleStatusesIds.keys()]
|
||||||
.map((id) => this.timeline.statuses.get(id))
|
.map((id) => this.timeline.statuses.get(id))
|
||||||
.filter(({ pinned }) => this.skipPinned ? !pinned : true)
|
.filter(({ pinned }) => (this.skipPinned ? !pinned : true))
|
||||||
},
|
},
|
||||||
newStatusCount() {
|
newStatusCount() {
|
||||||
return this.timeline.newStatusCount
|
return this.timeline.newStatusCount
|
||||||
|
|
@ -132,6 +132,7 @@ const Timeline = {
|
||||||
setTimeout(this.determineVisibleStatuses, 250)
|
setTimeout(this.determineVisibleStatuses, 250)
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
|
this.timelineChange(null, this.timelineRef)
|
||||||
window.removeEventListener('scroll', this.handleScroll)
|
window.removeEventListener('scroll', this.handleScroll)
|
||||||
window.removeEventListener('keydown', this.handleShortKey)
|
window.removeEventListener('keydown', this.handleShortKey)
|
||||||
if (document.hidden !== undefined)
|
if (document.hidden !== undefined)
|
||||||
|
|
@ -143,15 +144,16 @@ const Timeline = {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
timelineChange(newTimeline, oldTimeline) {
|
timelineChange(newTimeline, oldTimeline) {
|
||||||
// TODO this might not be necessary if we optimize mergeOrAdd
|
|
||||||
const sameName = newTimeline?.name === oldTimeline?.name
|
const sameName = newTimeline?.name === oldTimeline?.name
|
||||||
const sameArgument = newTimeline?.argument === oldTimeline?.argument
|
const sameArgument = newTimeline?.argument === oldTimeline?.argument
|
||||||
if (sameName && sameArgument) return
|
if (sameName && sameArgument) return
|
||||||
|
|
||||||
if (oldTimeline && oldTimeline.name !== 'friends') {
|
if (oldTimeline) {
|
||||||
useTimelinesStore().clearTimeline(oldTimeline.name)
|
useTimelinesStore().deactivate(oldTimeline.name)
|
||||||
|
}
|
||||||
|
if (newTimeline) {
|
||||||
|
useTimelinesStore().activate(newTimeline.name, newTimeline.argument)
|
||||||
}
|
}
|
||||||
useTimelinesStore().startFetchingTimeline(newTimeline.name, newTimeline.argument)
|
|
||||||
},
|
},
|
||||||
stopBlockingClicks: debounce(function () {
|
stopBlockingClicks: debounce(function () {
|
||||||
this.blockingClicks = false
|
this.blockingClicks = false
|
||||||
|
|
|
||||||
|
|
@ -79,12 +79,14 @@ const UserProfile = {
|
||||||
return useMergedConfigStore().mergedConfig.compactProfiles
|
return useMergedConfigStore().mergedConfig.compactProfiles
|
||||||
},
|
},
|
||||||
friends() {
|
friends() {
|
||||||
return [...useUsersStore().relationshipsLists.friends.get(this.user).keys()]
|
return [
|
||||||
.map((id) => useUsersStore().findUser(id))
|
...useUsersStore().relationshipsLists.friends.get(this.user).keys(),
|
||||||
|
].map((id) => useUsersStore().findUser(id))
|
||||||
},
|
},
|
||||||
followers() {
|
followers() {
|
||||||
return [...useUsersStore().relationshipsLists.followers.get(this.user).keys()]
|
return [
|
||||||
.map((id) => useUsersStore().findUser(id))
|
...useUsersStore().relationshipsLists.followers.get(this.user).keys(),
|
||||||
|
].map((id) => useUsersStore().findUser(id))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -93,7 +95,8 @@ const UserProfile = {
|
||||||
},
|
},
|
||||||
fetchUsers(group) {
|
fetchUsers(group) {
|
||||||
return () =>
|
return () =>
|
||||||
useUsersStore()['fetch' + group](this.userId)
|
useUsersStore()
|
||||||
|
['fetch' + group](this.userId)
|
||||||
.then((result) => ({ items: result }))
|
.then((result) => ({ items: result }))
|
||||||
},
|
},
|
||||||
load(userNameOrId) {
|
load(userNameOrId) {
|
||||||
|
|
|
||||||
|
|
@ -90,156 +90,6 @@ const api = {
|
||||||
return dispatch('stopMastoUserSocket')
|
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
|
|
||||||
|
|
||||||
const credentials = useOAuthStore().token
|
|
||||||
const url = getMastodonSocketURI({ credentials })
|
|
||||||
|
|
||||||
state.mastoUserSocket = ProcessedWS({
|
|
||||||
url,
|
|
||||||
id: 'Unified',
|
|
||||||
credentials,
|
|
||||||
})
|
|
||||||
|
|
||||||
state.mastoUserSocket.addEventListener(
|
|
||||||
'pleroma:authenticated',
|
|
||||||
() => {
|
|
||||||
state.mastoUserSocket.subscribe('user')
|
|
||||||
},
|
|
||||||
)
|
|
||||||
state.mastoUserSocket.addEventListener(
|
|
||||||
'message',
|
|
||||||
({ detail: message }) => {
|
|
||||||
if (!message) return // pings
|
|
||||||
if (message.event === 'notification') {
|
|
||||||
useNotificationsStore().addNewNotifications({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
data: message.notification,
|
|
||||||
})
|
|
||||||
} else if (message.event === 'update') {
|
|
||||||
useStatusesStore().addNewStatuses({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
statuses: [message.status],
|
|
||||||
userId: false,
|
|
||||||
showImmediately: timelineData.visibleStatuses.length === 0,
|
|
||||||
timeline: 'friends',
|
|
||||||
})
|
|
||||||
} else if (message.event === 'status.update') {
|
|
||||||
useStatusesStore().addNewStatuses({
|
|
||||||
timestamp: Date.now(),
|
|
||||||
statuses: [message.status],
|
|
||||||
userId: false,
|
|
||||||
showImmediately:
|
|
||||||
message.status.id in timelineData.visibleStatusesObject,
|
|
||||||
timeline: 'friends',
|
|
||||||
})
|
|
||||||
} else if (message.event === 'delete') {
|
|
||||||
dispatch('deleteStatusById', message.id)
|
|
||||||
} else if (message.event === 'pleroma:chat_update') {
|
|
||||||
// The setTimeout wrapper is a temporary band-aid to avoid duplicates for the user's own messages when doing optimistic sending.
|
|
||||||
// The cause of the duplicates is the WS event arriving earlier than the HTTP response.
|
|
||||||
// This setTimeout wrapper can be removed once the commit `8e41baff` is in the stable Pleroma release.
|
|
||||||
// (`8e41baff` adds the idempotency key to the chat message entity, which PleromaFE uses when it's available, and it makes this artificial delay unnecessary).
|
|
||||||
setTimeout(() => {
|
|
||||||
dispatch('addChatMessages', {
|
|
||||||
chatId: message.chatUpdate.id,
|
|
||||||
messages: [message.chatUpdate.lastMessage],
|
|
||||||
})
|
|
||||||
dispatch('updateChat', { chat: message.chatUpdate })
|
|
||||||
maybeShowChatNotification(store, message.chatUpdate)
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
state.mastoUserSocket.addEventListener('open', () => {
|
|
||||||
// Do not show notification when we just opened up the page
|
|
||||||
if (
|
|
||||||
state.mastoUserSocketStatus !==
|
|
||||||
WSConnectionStatus.STARTING_INITIAL
|
|
||||||
) {
|
|
||||||
useInterfaceStore().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')
|
|
||||||
useChatsStore().stopFetchingChats()
|
|
||||||
}
|
|
||||||
commit('resetRetryMultiplier')
|
|
||||||
commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)
|
|
||||||
})
|
|
||||||
state.mastoUserSocket.addEventListener(
|
|
||||||
'error',
|
|
||||||
({ detail: error }) => {
|
|
||||||
console.error('Error in MastoAPI websocket:', error)
|
|
||||||
// TODO is this needed?
|
|
||||||
dispatch('clearOpenedChats')
|
|
||||||
},
|
|
||||||
)
|
|
||||||
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')
|
|
||||||
useChatsStore().startFetchingChats()
|
|
||||||
useInterfaceStore().pushGlobalNotice({
|
|
||||||
level: 'error',
|
|
||||||
messageKey: 'timeline.socket_broke',
|
|
||||||
messageArgs: [code],
|
|
||||||
timeout: 5000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
|
|
||||||
}
|
|
||||||
dispatch('clearOpenedChats')
|
|
||||||
},
|
|
||||||
)
|
|
||||||
resolve()
|
|
||||||
} catch (e) {
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopMastoUserSocket({ state, dispatch }) {
|
|
||||||
dispatch('startFetchingTimeline', { timeline: 'friends' })
|
|
||||||
dispatch('startFetchingNotifications')
|
|
||||||
useChatsStore().startFetchingChats()
|
|
||||||
state.mastoUserSocket.close()
|
|
||||||
},
|
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
startFetchingNotifications(store) {
|
startFetchingNotifications(store) {
|
||||||
if (store.state.fetchers.notifications) return
|
if (store.state.fetchers.notifications) return
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,10 @@ const REPLY_VISIBILITY_TIMELINES = new Set([
|
||||||
'bubble',
|
'bubble',
|
||||||
])
|
])
|
||||||
|
|
||||||
const fetchAndUpdate = ({
|
const fetchAndUpdate = (
|
||||||
timeline,
|
{ timeline, argument, credentials },
|
||||||
argument,
|
{ maxId, sinceId, older = false, showImmediately = false },
|
||||||
credentials,
|
) => {
|
||||||
}, {
|
|
||||||
maxId,
|
|
||||||
sinceId,
|
|
||||||
older = false,
|
|
||||||
showImmediately = false,
|
|
||||||
}) => {
|
|
||||||
timeline.loading = true
|
timeline.loading = true
|
||||||
const { hideMutedPosts, replyVisibility } =
|
const { hideMutedPosts, replyVisibility } =
|
||||||
useMergedConfigStore().mergedConfig
|
useMergedConfigStore().mergedConfig
|
||||||
|
|
@ -71,15 +65,11 @@ const fetchAndUpdate = ({
|
||||||
.addNewStatuses({ statuses, timestamp })
|
.addNewStatuses({ statuses, timestamp })
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
||||||
useTimelinesStore().addStatusesToTimeline(
|
useTimelinesStore().addStatusesToTimeline(timeline.name, argument, {
|
||||||
timeline.name,
|
statuses,
|
||||||
argument,
|
showImmediately,
|
||||||
{
|
pagination,
|
||||||
statuses,
|
})
|
||||||
showImmediately,
|
|
||||||
pagination,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return { statuses, pagination }
|
return { statuses, pagination }
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
@ -101,7 +91,7 @@ const fetchAndUpdate = ({
|
||||||
|
|
||||||
const timelineFetcher = (timeline, argument, credentials) => {
|
const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
const state = {
|
const state = {
|
||||||
interval: null
|
interval: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const boundFetchAndUpdate = ({
|
const boundFetchAndUpdate = ({
|
||||||
|
|
@ -109,22 +99,26 @@ const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
maxId,
|
maxId,
|
||||||
sinceId,
|
sinceId,
|
||||||
older,
|
older,
|
||||||
} = {}) => fetchAndUpdate({
|
} = {}) =>
|
||||||
timeline,
|
fetchAndUpdate(
|
||||||
argument,
|
{
|
||||||
credentials,
|
timeline,
|
||||||
}, {
|
argument,
|
||||||
maxId,
|
credentials,
|
||||||
sinceId,
|
},
|
||||||
older,
|
{
|
||||||
showImmediately,
|
maxId,
|
||||||
})
|
sinceId,
|
||||||
|
older,
|
||||||
|
showImmediately,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const startFetching = () => {
|
const startFetching = () => {
|
||||||
if (state.interval) throw new Error('Interval already exists!')
|
if (state.interval) throw new Error('Interval already exists!')
|
||||||
|
|
||||||
boundFetchAndUpdate({
|
boundFetchAndUpdate({
|
||||||
showImmediately: timeline.visibleStatusesIds.size === 0
|
showImmediately: timeline.visibleStatusesIds.size === 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
state.interval = promiseInterval(boundFetchAndUpdate, 10000)
|
state.interval = promiseInterval(boundFetchAndUpdate, 10000)
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,9 @@ export const useNotificationsStore = defineStore('notifications', {
|
||||||
|
|
||||||
useUsersStore().addNewUsers({
|
useUsersStore().addNewUsers({
|
||||||
timestamp,
|
timestamp,
|
||||||
data: validNotifications.map((notification) => notification.from_profile),
|
data: validNotifications.map(
|
||||||
|
(notification) => notification.from_profile,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const statusNotifications = validNotifications.filter(
|
const statusNotifications = validNotifications.filter(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
|
||||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
import { useUsersStore } from 'src/stores/users.js'
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
import { useStreamingStore } from 'src/stores/streaming.js'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchEmojiReactions,
|
fetchEmojiReactions,
|
||||||
|
|
@ -78,7 +79,30 @@ const getLatestScrobble = (user) => {
|
||||||
export const useStatusesStore = defineStore('statuses', {
|
export const useStatusesStore = defineStore('statuses', {
|
||||||
state: defaultState,
|
state: defaultState,
|
||||||
actions: {
|
actions: {
|
||||||
addNewStatuses({ statuses, user = {}, userId, timestamp }) {
|
// Init
|
||||||
|
attachSocket() {
|
||||||
|
const et = new EventTarget()
|
||||||
|
const handleStatusMessage = ({ data, timestamp }) => {
|
||||||
|
console.log('STATUSES', data)
|
||||||
|
this.addNewStatuses({ statuses: [data.status], timestamp })
|
||||||
|
}
|
||||||
|
|
||||||
|
et.addEventListener('update', ({ detail: message }) => {
|
||||||
|
handleStatusMessage(message)
|
||||||
|
})
|
||||||
|
|
||||||
|
et.addEventListener('status.update', ({ detail: message }) => {
|
||||||
|
handleStatusMessage(message)
|
||||||
|
})
|
||||||
|
|
||||||
|
et.addEventListener('delete', ({ detail: message }) => {
|
||||||
|
console.log('DELETE', message)
|
||||||
|
this.deleteStatus(message.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
useStreamingStore().addSubscriber({ et })
|
||||||
|
},
|
||||||
|
addNewStatuses({ statuses, timestamp }) {
|
||||||
// Sanity check
|
// Sanity check
|
||||||
if (!Array.isArray(statuses)) {
|
if (!Array.isArray(statuses)) {
|
||||||
throw new TypeError("Statuses aren't an array!")
|
throw new TypeError("Statuses aren't an array!")
|
||||||
|
|
@ -132,7 +156,7 @@ export const useStatusesStore = defineStore('statuses', {
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
// This is our favorite, so the relevant bit.
|
// This is our favorite, so the relevant bit.
|
||||||
if (favorite.user.id === user.id) {
|
if (favorite.user.id === useUsersStore().currentUser?.id) {
|
||||||
status.favorited = true
|
status.favorited = true
|
||||||
} else {
|
} else {
|
||||||
status.fave_num += 1
|
status.fave_num += 1
|
||||||
|
|
|
||||||
189
src/stores/streaming.js
Normal file
189
src/stores/streaming.js
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
|
|
||||||
|
import {
|
||||||
|
getMastodonSocketURI,
|
||||||
|
ProcessedWS,
|
||||||
|
WSConnectionStatus,
|
||||||
|
} from 'src/api/websocket.js'
|
||||||
|
|
||||||
|
const ARGUMENT_MAP = {
|
||||||
|
tag: 'tag',
|
||||||
|
list: 'list',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TIMELINE_STREAM_MAP = {
|
||||||
|
friends: 'user',
|
||||||
|
public: 'public',
|
||||||
|
tag: 'hashtag',
|
||||||
|
list: 'list',
|
||||||
|
dms: 'direct',
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryTimeout = (multiplier) => 1000 * multiplier
|
||||||
|
|
||||||
|
export const useStreamingStore = defineStore('streaming', {
|
||||||
|
state: () => ({
|
||||||
|
socket: null,
|
||||||
|
error: null,
|
||||||
|
state: null,
|
||||||
|
retryMultiplier: 1,
|
||||||
|
subscribers: new Set(),
|
||||||
|
subscriptions: new Map(),
|
||||||
|
globalSubscriptions: new Set(),
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
addSubscriber(subscriber) {
|
||||||
|
const { stream, et } = subscriber
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
if (!this.subscriptions.has(stream.name)) {
|
||||||
|
this.subscriptions.set(stream.name, new Map())
|
||||||
|
}
|
||||||
|
|
||||||
|
const streamSubs = this.subscriptions.get(stream.name)
|
||||||
|
|
||||||
|
if (streamSubs.has(stream.argument)) {
|
||||||
|
throw new Error('Subscription already exists!')
|
||||||
|
}
|
||||||
|
|
||||||
|
streamSubs.set(stream.argument, subscriber)
|
||||||
|
} else {
|
||||||
|
this.globalSubscriptions.add(subscriber)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscribers.add(subscriber)
|
||||||
|
if (this.state === WSConnectionStatus.JOINED) {
|
||||||
|
this.socket.subscribe(...this.getSubArgs(stream))
|
||||||
|
subscriber.et.dispatchEvent(new CustomEvent('open'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeSubscriber(subscriber) {
|
||||||
|
const { stream, et } = subscriber
|
||||||
|
|
||||||
|
this.subscribers.delete(subscriber)
|
||||||
|
this.subscriptions.get(stream.name).delete(stream.argument)
|
||||||
|
|
||||||
|
if (this.state === WSConnectionStatus.JOINED) {
|
||||||
|
this.socket.unsubscribe(...this.getSubArgs(stream))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initSocket(initial) {
|
||||||
|
this.state = initial
|
||||||
|
? WSConnectionStatus.STARTING_INITIAL
|
||||||
|
: WSConnectionStatus.STARTING
|
||||||
|
|
||||||
|
const credentials = useOAuthStore().token
|
||||||
|
const url = getMastodonSocketURI({ credentials })
|
||||||
|
|
||||||
|
this.socket = ProcessedWS({
|
||||||
|
url,
|
||||||
|
id: 'Unified',
|
||||||
|
credentials,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.socket.addEventListener('pleroma:authenticated', this.onAuth)
|
||||||
|
this.socket.addEventListener('open', this.onOpen)
|
||||||
|
this.socket.addEventListener('close', this.onClose)
|
||||||
|
this.socket.addEventListener('message', this.onMessage)
|
||||||
|
this.socket.addEventListener('error', this.onError)
|
||||||
|
},
|
||||||
|
stopSocket() {
|
||||||
|
this.socket.close()
|
||||||
|
},
|
||||||
|
|
||||||
|
getSubArgs(stream) {
|
||||||
|
const argumentKey = ARGUMENT_MAP[stream.name]
|
||||||
|
const args = argumentKey
|
||||||
|
? {
|
||||||
|
[argumentKey]: stream.argument,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
return [stream.name, args]
|
||||||
|
},
|
||||||
|
onAuth() {
|
||||||
|
this.subscribers.forEach(({ stream, et }) => {
|
||||||
|
et.dispatchEvent(new CustomEvent('authenticated'))
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
this.socket.subscribe(...this.getSubArgs(stream))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.state = WSConnectionStatus.JOINED
|
||||||
|
},
|
||||||
|
onOpen() {
|
||||||
|
this.subscribers.forEach(({ stream, et }) => {
|
||||||
|
et.dispatchEvent(new CustomEvent('open'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onMessage({ detail: message }) {
|
||||||
|
if (!message) return // pings
|
||||||
|
const timestamp = Date.now()
|
||||||
|
const { event: eventName, stream: eventStream, ...data } = message
|
||||||
|
const [streamName, streamArgument] = eventStream ?? []
|
||||||
|
|
||||||
|
const subscriber = this.subscriptions.get(streamName)?.get(streamArgument)
|
||||||
|
|
||||||
|
const totalSubs = [
|
||||||
|
...this.globalSubscriptions.values(),
|
||||||
|
subscriber
|
||||||
|
].filter(Boolean)
|
||||||
|
|
||||||
|
totalSubs.forEach(({ stream, et }) => {
|
||||||
|
et.dispatchEvent(new CustomEvent(
|
||||||
|
eventName,
|
||||||
|
{
|
||||||
|
detail: { streamName, streamArgument, data, timestamp },
|
||||||
|
}
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('WS', message)
|
||||||
|
},
|
||||||
|
onError({ detail: error }) {
|
||||||
|
this.subscribers.forEach(({ stream, et }) => {
|
||||||
|
et.dispatchEvent(new CustomEvent('error', error))
|
||||||
|
})
|
||||||
|
console.error('Error in MastoAPI websocket:', error)
|
||||||
|
},
|
||||||
|
onClose({ 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`,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.state = WSConnectionStatus.CLOSED
|
||||||
|
|
||||||
|
this.subscribers.forEach(({ et }) => {
|
||||||
|
et.dispatchEvent(new CustomEvent('close', closeEvent))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initSocket()
|
||||||
|
}, retryTimeout(this.retryMultiplier))
|
||||||
|
|
||||||
|
this.retryMultiplier += 1
|
||||||
|
|
||||||
|
if (this.state !== WSConnectionStatus.ERROR) {
|
||||||
|
this.subscribers.forEach(({ et }) => {
|
||||||
|
et.dispatchEvent(new CustomEvent('close', closeEvent))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = WSConnectionStatus.ERROR
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -4,6 +4,8 @@ import { defineStore } from 'pinia'
|
||||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
import { useUsersStore } from 'src/stores/users.js'
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||||
|
import { useStreamingStore, TIMELINE_STREAM_MAP } from 'src/stores/streaming.js'
|
||||||
|
|
||||||
import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js'
|
import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js'
|
||||||
|
|
||||||
|
|
@ -17,6 +19,7 @@ const emptyTl = (name, argument = null) => {
|
||||||
minId: '',
|
minId: '',
|
||||||
minVisibleId: '',
|
minVisibleId: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
|
streaming: false,
|
||||||
flushMarker: 0,
|
flushMarker: 0,
|
||||||
fetcher: null,
|
fetcher: null,
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +30,10 @@ const emptyTl = (name, argument = null) => {
|
||||||
result[property] = argument
|
result[property] = argument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'dms' || name === 'friends') {
|
||||||
|
result.persistent = true
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,24 +48,28 @@ export const ARGUMENT_MAP = {
|
||||||
media: 'userId',
|
media: 'userId',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TIMELINES = new Set([
|
||||||
|
'mentions',
|
||||||
|
'public',
|
||||||
|
'user',
|
||||||
|
'userPinned',
|
||||||
|
'media',
|
||||||
|
'favorites',
|
||||||
|
'publicAndExternal',
|
||||||
|
'friends',
|
||||||
|
'tag',
|
||||||
|
'dms',
|
||||||
|
'bookmarks',
|
||||||
|
'list',
|
||||||
|
'bubble',
|
||||||
|
'quotes',
|
||||||
|
'search',
|
||||||
|
])
|
||||||
|
|
||||||
export const defaultState = () => {
|
export const defaultState = () => {
|
||||||
return Object.fromEntries([
|
return Object.fromEntries(
|
||||||
'mentions',
|
[...TIMELINES].map((name) => [name, emptyTl(name)]),
|
||||||
'public',
|
)
|
||||||
'user',
|
|
||||||
'userPinned',
|
|
||||||
'media',
|
|
||||||
'favorites',
|
|
||||||
'publicAndExternal',
|
|
||||||
'friends',
|
|
||||||
'tag',
|
|
||||||
'dms',
|
|
||||||
'bookmarks',
|
|
||||||
'list',
|
|
||||||
'bubble',
|
|
||||||
'quotes',
|
|
||||||
'search',
|
|
||||||
].map((name) => [name, emptyTl(name)]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//const CUSTOM_SORT = new Set(['bookmarks', 'favorites'])
|
//const CUSTOM_SORT = new Set(['bookmarks', 'favorites'])
|
||||||
|
|
@ -123,7 +134,10 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
) {
|
) {
|
||||||
// Add the mention to the mentions timeline
|
// Add the mention to the mentions timeline
|
||||||
if (timeline !== this.mentions) {
|
if (timeline !== this.mentions) {
|
||||||
this.addStatusesToTimeline('mentions', null, { statuses, nested: true })
|
this.addStatusesToTimeline('mentions', null, {
|
||||||
|
statuses,
|
||||||
|
nested: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,10 +149,17 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// Fetchers
|
activatePersistents() {
|
||||||
startFetchingTimeline(timelineName, argument) {
|
TIMELINES.forEach(name => {
|
||||||
|
if (this[name].persistent) {
|
||||||
|
this.activate(name, undefined, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
activate(timelineName, argument, persistent) {
|
||||||
const timeline = this[timelineName]
|
const timeline = this[timelineName]
|
||||||
if (timeline.fetcher) return
|
if (timeline.persistent && !persistent) return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
timelineName === 'favourites' &&
|
timelineName === 'favourites' &&
|
||||||
|
|
@ -153,12 +174,64 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
useOAuthStore().token,
|
useOAuthStore().token,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.startFetchingTimeline(timelineName, argument)
|
||||||
|
|
||||||
|
const streamName = TIMELINE_STREAM_MAP[timelineName]
|
||||||
|
|
||||||
|
if (streamName) {
|
||||||
|
const et = new EventTarget()
|
||||||
|
et.addEventListener('open', () => this.onStreamConnect(timelineName, argument))
|
||||||
|
et.addEventListener('close', () => this.onStreamDisconnect(timelineName, argument))
|
||||||
|
et.addEventListener('update', ({ detail: message }) => this.onStreamMessage(timelineName, argument, message))
|
||||||
|
|
||||||
|
timeline.socket = {
|
||||||
|
stream: {
|
||||||
|
name: streamName,
|
||||||
|
argument,
|
||||||
|
},
|
||||||
|
et,
|
||||||
|
}
|
||||||
|
|
||||||
|
useStreamingStore().addSubscriber(timeline.socket)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deactivate(timelineName) {
|
||||||
|
const timeline = this[timelineName]
|
||||||
|
if (timeline.persistent) return
|
||||||
|
this.clearTimeline(timelineName)
|
||||||
|
},
|
||||||
|
|
||||||
|
onStreamMessage(timeline, argument, event) {
|
||||||
|
// This relies on statuses store to process this event first
|
||||||
|
const status = useStatusesStore().allStatuses.get(event.data.status.id)
|
||||||
|
|
||||||
|
this.addStatusesToTimeline(timeline, argument, {
|
||||||
|
statuses: [status],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onStreamConnect(timeline) {
|
||||||
|
console.log('STREAM OK', timeline)
|
||||||
|
this[timeline].streaming = true
|
||||||
|
this.stopFetchingTimeline(timeline)
|
||||||
|
},
|
||||||
|
|
||||||
|
onStreamDisconnect(timeline, argument) {
|
||||||
|
console.log('STREAM DED', timeline, argument)
|
||||||
|
this[timeline].streaming = false
|
||||||
|
this.startFetchingTimeline(timeline, argument)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetchers
|
||||||
|
startFetchingTimeline(timelineName, argument) {
|
||||||
|
console.log('START FETCHING', timelineName, argument)
|
||||||
|
const timeline = this[timelineName]
|
||||||
timeline.fetcher.startFetching()
|
timeline.fetcher.startFetching()
|
||||||
},
|
},
|
||||||
stopFetchingTimeline(timelineName) {
|
stopFetchingTimeline(timelineName) {
|
||||||
|
console.log('STOP FETCHING', timelineName)
|
||||||
const timeline = this[timelineName]
|
const timeline = this[timelineName]
|
||||||
timeline.fetcher?.stopFetching()
|
timeline.fetcher.stopFetching()
|
||||||
timeline.fetcher = null
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Queues & Timeline manip
|
// Queues & Timeline manip
|
||||||
|
|
@ -198,10 +271,16 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
|
|
||||||
this.updateTimelineExtremes(timeline, [...timeline.statuses.keys()])
|
this.updateTimelineExtremes(timeline, [...timeline.statuses.keys()])
|
||||||
},
|
},
|
||||||
clearTimeline(timeline, excludeUserId = false) {
|
clearTimeline(timeline) {
|
||||||
const userId = excludeUserId ? this[timeline].userId : undefined
|
console.log('CLEAR TIMELINE', timeline)
|
||||||
this.stopFetchingTimeline(timeline)
|
const data = this[timeline]
|
||||||
this[timeline] = emptyTl(timeline, userId)
|
if (!data.streaming) {
|
||||||
|
this.stopFetchingTimeline(timeline)
|
||||||
|
}
|
||||||
|
if (data.socket) {
|
||||||
|
useStreamingStore().removeSubscriber(data.socket)
|
||||||
|
}
|
||||||
|
this[timeline] = emptyTl(timeline)
|
||||||
},
|
},
|
||||||
queueFlush(timeline, id) {
|
queueFlush(timeline, id) {
|
||||||
this[timeline].flushMarker = id
|
this[timeline].flushMarker = id
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,9 @@ import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||||
import { useNotificationsStore } from 'src/stores/notifications.js'
|
import { useNotificationsStore } from 'src/stores/notifications.js'
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||||
import { useTimelinesStore } from 'src/stores/timelines.js'
|
|
||||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||||
|
import { useTimelinesStore } from 'src/stores/timelines.js'
|
||||||
|
import { useStreamingStore } from 'src/stores/streaming.js'
|
||||||
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
||||||
|
|
||||||
import { revokeToken } from 'src/api/oauth.js'
|
import { revokeToken } from 'src/api/oauth.js'
|
||||||
|
|
@ -391,9 +392,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
fetchUserRelationship({
|
fetchUserRelationship({
|
||||||
id,
|
id,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then((result) =>
|
}).then((result) => this.updateUserRelationships(result))
|
||||||
this.updateUserRelationships(result),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchUserInLists(id) {
|
fetchUserInLists(id) {
|
||||||
|
|
@ -431,7 +430,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
const predictedRelationship = this.relationships[id] || { id }
|
const predictedRelationship = this.relationships[id] || { id }
|
||||||
this.updateUserRelationships({
|
this.updateUserRelationships({
|
||||||
optimism: true,
|
optimism: true,
|
||||||
data: [predictedRelationship]
|
data: [predictedRelationship],
|
||||||
})
|
})
|
||||||
this.addBlockId(id)
|
this.addBlockId(id)
|
||||||
|
|
||||||
|
|
@ -494,7 +493,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
predictedRelationship.muting = true
|
predictedRelationship.muting = true
|
||||||
this.updateUserRelationships({
|
this.updateUserRelationships({
|
||||||
optimism: true,
|
optimism: true,
|
||||||
data: [predictedRelationship]
|
data: [predictedRelationship],
|
||||||
})
|
})
|
||||||
this.addMuteId(id)
|
this.addMuteId(id)
|
||||||
|
|
||||||
|
|
@ -512,7 +511,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
predictedRelationship.muting = false
|
predictedRelationship.muting = false
|
||||||
this.updateUserRelationships({
|
this.updateUserRelationships({
|
||||||
optimism: true,
|
optimism: true,
|
||||||
data: [predictedRelationship]
|
data: [predictedRelationship],
|
||||||
})
|
})
|
||||||
|
|
||||||
return unmuteUser({ id }).then(({ data: relationship }) =>
|
return unmuteUser({ id }).then(({ data: relationship }) =>
|
||||||
|
|
@ -524,18 +523,14 @@ export const useUsersStore = defineStore('users', {
|
||||||
id,
|
id,
|
||||||
reblogs: false,
|
reblogs: false,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: relationship }) =>
|
}).then(({ data: relationship }) => this.updateUserRelationships(data))
|
||||||
this.updateUserRelationships(data),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
showReblogs(id) {
|
showReblogs(id) {
|
||||||
return followUser({
|
return followUser({
|
||||||
id,
|
id,
|
||||||
reblogs: true,
|
reblogs: true,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: relationship }) =>
|
}).then(({ data: relationship }) => this.updateUserRelationships(data))
|
||||||
this.updateUserRelationships(data),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
muteUsers(data = []) {
|
muteUsers(data = []) {
|
||||||
return Promise.all(data.map((d) => this.muteUser(d)))
|
return Promise.all(data.map((d) => this.muteUser(d)))
|
||||||
|
|
@ -600,18 +595,14 @@ export const useUsersStore = defineStore('users', {
|
||||||
id,
|
id,
|
||||||
notify: true,
|
notify: true,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: relationship }) =>
|
}).then(({ data: relationship }) => this.updateUserRelationships(data))
|
||||||
this.updateUserRelationships(data),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
unsubscribeUser(id) {
|
unsubscribeUser(id) {
|
||||||
return followUser({
|
return followUser({
|
||||||
id,
|
id,
|
||||||
notify: false,
|
notify: false,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: relationship }) =>
|
}).then(({ data: relationship }) => this.updateUserRelationships(data))
|
||||||
this.updateUserRelationships(data),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
registerPushNotifications() {
|
registerPushNotifications() {
|
||||||
const token = this.currentUser.credentials
|
const token = this.currentUser.credentials
|
||||||
|
|
@ -729,55 +720,31 @@ export const useUsersStore = defineStore('users', {
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
if (user.token) {
|
if (user.token) {
|
||||||
|
// Shoutbox
|
||||||
dispatch('setWsToken', user.token)
|
dispatch('setWsToken', user.token)
|
||||||
|
|
||||||
// Initialize the shout socket.
|
|
||||||
dispatch('initializeSocket')
|
dispatch('initializeSocket')
|
||||||
}
|
}
|
||||||
|
|
||||||
const startPolling = () => {
|
// DMs and Home
|
||||||
// Start getting fresh posts.
|
useTimelinesStore().activatePersistents()
|
||||||
useTimelinesStore().startFetchingTimeline('friends')
|
|
||||||
|
|
||||||
// Start fetching notifications
|
if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
|
||||||
dispatch('startFetchingNotifications')
|
// Start fetching chats
|
||||||
|
useChatsStore().startFetchingChats()
|
||||||
if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
|
|
||||||
// Start fetching chats
|
|
||||||
useChatsStore().startFetchingChats()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useListsStore().startFetching()
|
useListsStore().startFetching()
|
||||||
useBookmarkFoldersStore().startFetching()
|
useBookmarkFoldersStore().startFetching()
|
||||||
|
useStatusesStore().attachSocket()
|
||||||
|
//useNotificationsStore().attachSocket()
|
||||||
|
|
||||||
if (user.locked) {
|
if (user.locked) {
|
||||||
dispatch('startFetchingFollowRequests')
|
dispatch('startFetchingFollowRequests')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useStreamingStore().initSocket()
|
||||||
if (useMergedConfigStore().mergedConfig.useStreamingApi) {
|
if (useMergedConfigStore().mergedConfig.useStreamingApi) {
|
||||||
dispatch('fetchTimeline', {
|
useStreamingStore().initSocket()
|
||||||
timeline: 'friends',
|
|
||||||
sinceId: null,
|
|
||||||
})
|
|
||||||
dispatch('fetchNotifications', { sinceId: null })
|
|
||||||
dispatch('enableMastoSockets', true)
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(
|
|
||||||
'Failed initializing MastoAPI Streaming socket',
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
dispatch('fetchChats', { latest: true })
|
|
||||||
setTimeout(
|
|
||||||
() =>
|
|
||||||
useNotificationsStore().setNotificationsSilence(false),
|
|
||||||
10000,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
startPolling()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start fetching things that don't need to block the UI
|
// Start fetching things that don't need to block the UI
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue