pleroma-fe/src/stores/users.js

790 lines
23 KiB
JavaScript
Raw Normal View History

2026-08-10 13:52:45 +03:00
import Cookies from 'js-cookie'
2026-08-18 03:20:09 +03:00
import { last } from 'lodash'
2026-08-10 13:52:45 +03:00
import { defineStore } from 'pinia'
import { WSConnectionStatus } from 'src/api/websocket.js'
2026-08-10 13:52:45 +03:00
import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useEmojiStore } from 'src/stores/emoji.js'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useListsStore } from 'src/stores/lists.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
2026-08-10 22:26:22 +03:00
import { useNotificationsStore } from 'src/stores/notifications.js'
2026-08-10 13:52:45 +03:00
import { useOAuthStore } from 'src/stores/oauth.js'
2026-08-10 22:26:22 +03:00
import { useStatusesStore } from 'src/stores/statuses.js'
2026-08-13 01:22:44 +03:00
import { useStreamingStore } from 'src/stores/streaming.js'
2026-08-10 13:52:45 +03:00
import { useSyncConfigStore } from 'src/stores/sync_config.js'
2026-08-13 01:12:48 +03:00
import { useTimelinesStore } from 'src/stores/timelines.js'
2026-08-10 13:52:45 +03:00
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
import { revokeToken } from 'src/api/oauth.js'
import {
fetchFollowers,
fetchFriends,
fetchUser,
fetchUserByName,
verifyCredentials,
} from 'src/api/public.js'
import {
blockUser,
editUserNote,
fetchBlocks,
fetchDomainMutes,
fetchMutes,
fetchUserInLists,
fetchUserRelationship,
followUser,
muteDomain,
muteUser,
removeUserFromFollowers,
unblockUser,
2026-08-18 03:20:09 +03:00
unfollowUser,
2026-08-10 13:52:45 +03:00
unmuteDomain,
unmuteUser,
} from 'src/api/user.js'
2026-08-18 03:20:09 +03:00
import { promiseInterval } from 'src/services/promise_interval/promise_interval.js'
2026-08-10 13:52:45 +03:00
export const useUsersStore = defineStore('users', {
2026-08-10 15:00:59 +03:00
state: () => ({
loggingIn: false,
lastLoginName: null,
currentUser: null,
users: new Map(),
usersByName: new Map(),
usersByURL: new Map(),
relationships: new Map(),
2026-08-12 16:31:28 +03:00
relationshipsLists: {
friends: new WeakMap(),
followers: new WeakMap(),
},
2026-08-10 15:48:27 +03:00
timestamps: new WeakMap(),
2026-08-10 16:22:33 +03:00
fetchesIds: new Map(),
fetchesNames: new Map(),
2026-08-18 03:20:09 +03:00
followPollers: new Map(),
followPollersAttempts: new Map(),
2026-08-10 15:00:59 +03:00
}),
2026-08-10 13:52:45 +03:00
getters: {
loggedIn: (state) => !!state.currentUser,
findUser: (state) => (query) => {
return state.users.get(query)
},
findUserByName: (state) => (query) => {
return state.usersByName.get(query.toLowerCase())
},
findUserByUrl: (state) => (query) => {
return state.usersByURL.get(query.toLowerCase())
},
relationship: (state) => (id) => {
const rel = id && state.relationships.get(id)
return rel || { id, loading: true }
},
},
actions: {
2026-08-18 03:20:09 +03:00
// Main updates
2026-08-10 15:48:27 +03:00
addNewUsers(response) {
const { data, timestamp } = response
const users = Array.isArray(data) ? data : [data]
2026-08-10 22:26:22 +03:00
return users.map((user) => {
2026-08-10 15:00:59 +03:00
const existing = this.users.get(user.id) ?? {}
2026-08-10 15:48:27 +03:00
const oldTimestamp = this.timestamps.get(existing)
2026-08-18 03:20:09 +03:00
//
2026-08-12 18:01:00 +03:00
// Relationship might have different timestamp and
// might need updating separate from user
2026-08-18 03:20:09 +03:00
const oldRelationship = this.relationships.get(user.id)
const oldRelationshipTimestamp = this.timestamps.get(oldRelationship)
let relationship
// Only need an update if new user data has relationship
if (user.relationship) {
// Only update if there is no old data or if it's outdated
if (
oldRelationship === undefined ||
timestamp > oldRelationshipTimestamp
) {
relationship = this.updateUserRelationships({
timestamp,
data: { id: user.id, ...user.relationship },
})[0]
}
} else {
relationship = oldRelationship
}
2026-08-12 18:01:00 +03:00
2026-08-10 15:48:27 +03:00
// implicit: if oldTimestamp is undefined this will still be false
2026-08-10 22:26:22 +03:00
if (oldTimestamp > timestamp) return existing // not overwriting old data with new
2026-08-10 13:52:45 +03:00
2026-08-12 18:01:00 +03:00
const { relationship: unused1, ...newUser } = user
2026-08-12 15:56:31 +03:00
2026-08-14 19:49:12 +03:00
let reactive = this.users.get(user.id)
// Initializing reactivity & avoiding excessive Map mutation
if (!reactive) {
this.users.set(user.id, existing)
reactive = this.users.get(user.id)
this.usersByName.set(user.screen_name.toLowerCase(), reactive)
this.usersByURL.set(user.url.toLowerCase(), reactive)
}
2026-08-12 16:31:28 +03:00
// Relying on object reactivity to avoid mutating the Map
2026-08-18 03:20:09 +03:00
reactive.relationship = relationship ?? reactive.relationship
2026-08-12 18:01:00 +03:00
2026-08-12 16:31:28 +03:00
Object.entries(newUser).forEach(([k, v]) => {
2026-08-12 18:01:00 +03:00
reactive[k] = v
2026-08-12 16:31:28 +03:00
})
2026-08-12 15:56:31 +03:00
2026-08-12 18:01:00 +03:00
// Updating the timestamp
this.timestamps.set(reactive, timestamp)
2026-08-14 19:38:37 +03:00
if (user.id === this.currentUser?.id) {
2026-08-12 18:01:00 +03:00
this.currentUser = reactive
2026-08-10 15:00:59 +03:00
}
2026-08-10 22:26:22 +03:00
2026-08-12 18:01:00 +03:00
// Initialize some stuff
2026-08-12 16:31:28 +03:00
const { friends, followers } = this.relationshipsLists
2026-08-12 18:01:00 +03:00
if (!friends.has(reactive)) friends.set(reactive, new Set())
if (!followers.has(reactive)) followers.set(reactive, new Set())
2026-08-12 16:31:28 +03:00
2026-08-12 18:01:00 +03:00
return reactive
2026-08-10 13:52:45 +03:00
})
},
2026-08-12 18:01:00 +03:00
updateUserRelationships({ timestamp, optimism, data }) {
const relationships = Array.isArray(data) ? data : [data]
return relationships.map((relationship) => {
const { id } = relationship
const existing = this.relationships.get(id) ?? {}
const oldTimestamp = this.timestamps.get(existing)
// implicit: if oldTimestamp is undefined this will still be false
2026-08-18 03:20:09 +03:00
if (!optimism && oldTimestamp > timestamp) return existing
2026-08-12 18:01:00 +03:00
// Initializing reactivity
if (!this.relationships.has(id)) this.relationships.set(id, existing)
const reactive = this.relationships.get(id)
// Relying on reactivity
Object.entries(relationship).forEach(([k, v]) => {
reactive[k] = v
})
if (timestamp) {
2026-08-18 03:20:09 +03:00
this.timestamps.set(reactive, timestamp)
2026-08-12 18:01:00 +03:00
}
// Updating user property if there is such a user
if (this.users.has(id)) {
this.users.get(id).relationship = reactive
}
2026-08-18 03:20:09 +03:00
// Update block/mute lists
if (this.currentUser && id !== this.currentUser.id) {
;[
['muting', this.currentUser.muteIds],
['blocking', this.currentUser.blockIds],
[
'following',
this.relationshipsLists.friends.get(this.currentUser),
],
[
'followed_by',
this.relationshipsLists.followers.get(this.currentUser),
],
].forEach(([relationshipName, list]) => {
if (relationship[relationshipName]) {
list?.add(id)
} else {
list?.delete(id)
}
})
}
2026-08-12 18:01:00 +03:00
return reactive
2026-08-10 13:52:45 +03:00
})
},
2026-08-18 03:20:09 +03:00
// Misc updates
updateUserAdminData(id, data) {
const user = this.users.get(id)
user.adminData = data
user.deactivated = !data.is_active
user.tags = new Set(data.tags)
2026-08-10 13:52:45 +03:00
},
2026-08-18 03:20:09 +03:00
updateRight(id, right, value) {
const user = this.users.get(id)
const newRights = user.rights ?? {}
newRights[right] = value
user.rights = newRights
2026-08-10 13:52:45 +03:00
},
2026-08-18 03:20:09 +03:00
// Because frontend doesn't have a reason to keep these stuff in memory
// outside of viewing someones user profile.
clearFollowLists(userId) {
const user = this.users.get(userId)
if (user) {
this.relationshipsLists.friends.set(user, new Set())
this.relationshipsLists.followers.set(user, new Set())
2026-08-10 13:52:45 +03:00
}
},
2026-08-18 03:20:09 +03:00
// Fetches
2026-08-10 16:22:33 +03:00
async fetchUserIfMissing({ id, name }) {
let findFunc
let fetchFunc
let map
let identifier
if (id) {
findFunc = this.findUser
fetchFunc = this.fetchUser
map = this.fetchesIds
identifier = id
} else if (name) {
findFunc = this.findUserByName
fetchFunc = this.fetchUserByName
map = this.fetchesNames
identifier = name
} else {
throw new TypeError('No identifier provided')
}
// Search in cache
2026-08-10 16:22:33 +03:00
const user = findFunc(identifier)
2026-08-18 03:20:09 +03:00
if (user) return user
// not found => fetch
2026-08-18 03:20:09 +03:00
let promise
2026-08-10 16:22:33 +03:00
2026-08-18 03:20:09 +03:00
// Did we already search for this user?
if (map.has(identifier)) {
// if so, reuse the promise
promise = map.get(identifier)
} else {
// if not, make a new one
promise = fetchFunc(identifier)
}
2026-08-10 16:22:33 +03:00
2026-08-18 03:20:09 +03:00
map.set(identifier, promise)
2026-08-10 16:22:33 +03:00
2026-08-18 03:20:09 +03:00
const result = await promise
2026-08-10 16:22:33 +03:00
2026-08-18 03:20:09 +03:00
if (result) {
const { id, screen_name } = result
2026-08-10 16:22:33 +03:00
2026-08-18 03:20:09 +03:00
// Save promise for future use
this.fetchesIds.set(id, promise)
this.fetchesNames.set(screen_name, promise)
return this.users.get(id)
2026-08-10 13:52:45 +03:00
} else {
2026-08-18 03:20:09 +03:00
return null
2026-08-10 13:52:45 +03:00
}
},
2026-08-10 16:22:33 +03:00
async fetchUser(id) {
try {
const result = await fetchUser({
id,
credentials: useOAuthStore().token,
2026-08-10 13:52:45 +03:00
})
2026-08-10 16:22:33 +03:00
this.addNewUsers(result)
2026-08-10 16:45:52 +03:00
2026-08-10 16:22:33 +03:00
return this.users.get(result.data.id)
} catch (error) {
if (error.name === 'StatusCodeError' && error.statusCode === 404) {
2026-08-10 16:22:33 +03:00
console.warn(`User ${id} not found`)
return null
} else {
throw error
}
}
2026-08-10 13:52:45 +03:00
},
2026-08-10 16:22:33 +03:00
async fetchUserByName(name) {
try {
2026-08-10 16:45:52 +03:00
const result = await fetchUserByName({
2026-08-10 16:22:33 +03:00
name,
credentials: useOAuthStore().token,
})
this.addNewUsers(result)
return this.users.get(result.data.id)
} catch (error) {
if (error.name === 'StatusCodeError' && error.statusCode === 404) {
console.warn(`User ${name} not found`)
2026-08-10 16:22:33 +03:00
return null
} else {
throw error
}
}
2026-08-10 13:52:45 +03:00
},
fetchUserRelationship(id) {
2026-08-18 03:20:09 +03:00
return fetchUserRelationship({
id,
credentials: useOAuthStore().token,
}).then((result) => this.updateUserRelationships(result))
},
fetchFriends(id) {
const user = this.users.get(id)
const maxId = last([...this.relationshipsLists.friends.get(user)])
return fetchFriends({
id,
maxId,
credentials: useOAuthStore().token,
}).then((result) => {
const users = this.addNewUsers(result)
const list = this.relationshipsLists.friends.get(user)
users.forEach(({ id }) => list.add(id))
return result.data
})
},
fetchFollowers(id) {
const user = this.users.get(id)
const maxId = last([...this.relationshipsLists.followers.get(user)])
return fetchFollowers({
id,
maxId,
credentials: useOAuthStore().token,
}).then((result) => {
const users = this.addNewUsers(result)
const list = this.relationshipsLists.followers.get(user)
users.forEach(({ id }) => list.add(id))
return result.data
})
2026-08-10 13:52:45 +03:00
},
fetchUserInLists(id) {
if (this.currentUser) {
2026-08-18 03:20:09 +03:00
return fetchUserInLists({
2026-08-10 13:52:45 +03:00
id,
credentials: useOAuthStore().token,
2026-08-18 03:20:09 +03:00
}).then(({ data: inLists }) => {
this.users.get(id).inLists = inLists
})
2026-08-10 13:52:45 +03:00
}
},
2026-08-18 03:20:09 +03:00
fetchMutes(args) {
const { reset } = args || {}
const maxId = this.currentUser.muteIdsMaxId
return fetchMutes({
maxId,
credentials: useOAuthStore().token,
}).then((result) => {
const { data: mutes } = result
if (reset) {
this.currentUser.muteIds = new Set(mutes.map(({ id }) => id))
} else {
mutes.forEach(({ id }) => this.currentUser.muteIds.add(id))
}
if (mutes.length) {
this.currentUser.muteIdsMaxId = last(mutes).id
}
this.addNewUsers(result)
return mutes
})
},
2026-08-10 13:52:45 +03:00
fetchBlocks(args) {
const { reset } = args || {}
const maxId = this.currentUser.blockIdsMaxId
return fetchBlocks({
maxId,
credentials: useOAuthStore().token,
2026-08-10 15:48:27 +03:00
}).then((result) => {
const { data: blocks } = result
2026-08-10 13:52:45 +03:00
if (reset) {
2026-08-18 03:20:09 +03:00
this.currentUser.blockIds = new Set(blocks.map(({ id }) => id))
2026-08-10 13:52:45 +03:00
} else {
2026-08-18 03:20:09 +03:00
blocks.forEach(({ id }) => this.currentUser.blockIds.add(id))
2026-08-10 13:52:45 +03:00
}
if (blocks.length) {
2026-08-18 03:20:09 +03:00
this.currentUser.blockIdsMaxId = last(blocks).id
2026-08-10 13:52:45 +03:00
}
2026-08-10 15:48:27 +03:00
this.addNewUsers(result)
2026-08-10 13:52:45 +03:00
return blocks
})
},
2026-08-18 03:20:09 +03:00
fetchDomainMutes() {
return fetchDomainMutes({
credentials: useOAuthStore().token,
}).then(({ data: domainMutes }) => {
this.currentUser.domainMutes = new Set(domainMutes)
return domainMutes
2026-08-12 18:01:00 +03:00
})
2026-08-18 03:20:09 +03:00
},
2026-08-10 13:52:45 +03:00
2026-08-18 03:20:09 +03:00
// Actions
/// Follow
async followUser(id) {
// Don't spam follow requests if we are already polling
if (this.followPollers.has(id)) return
2026-08-10 13:52:45 +03:00
2026-08-18 03:20:09 +03:00
const followFunc = () =>
followUser({
id,
credentials: useOAuthStore().token,
}).then((result) => this.updateUserRelationships(result))
const checker = async (func = () => this.fetchUserRelationship(id)) => {
await func() // Either fetch relationships or follow request
const relationship = this.relationships.get(id)
return (
relationship.following ||
(relationship.locked && relationship.requested)
)
}
const immediate = await checker(followFunc)
if (immediate) return // If follow goes through immediately don'd to looping
const loop = async () => {
const attempts = this.followPollersAttempts.get(id)
const result = await checker()
if (result || attempts === 1) {
this.followPollersAttempts.delete(id)
this.followPollers.get(id).stop()
this.followPollers.delete(id)
} else {
this.followPollersAttempts.set(id, attempts - 1)
}
}
this.followPollersAttempts.set(id, 3)
this.followPollers.set(id, promiseInterval(loop, 500))
},
async unfollowUser(id) {
const result = await unfollowUser({
id,
credentials: useOAuthStore().token,
2026-08-10 13:52:45 +03:00
})
2026-08-18 03:20:09 +03:00
return this.updateUserRelationships(result)
2026-08-10 13:52:45 +03:00
},
2026-08-18 03:20:09 +03:00
/// Subscribe
subscribeUser(id) {
return followUser({
id,
notify: true,
credentials: useOAuthStore().token,
}).then((result) => this.updateUserRelationships(result))
2026-08-10 13:52:45 +03:00
},
2026-08-18 03:20:09 +03:00
unsubscribeUser(id) {
return followUser({
id,
notify: false,
credentials: useOAuthStore().token,
}).then((result) => this.updateUserRelationships(result))
},
/// User Note
editUserNote(id, comment) {
return editUserNote({ id, comment }).then((result) =>
this.updateUserRelationships(result),
2026-08-10 13:52:45 +03:00
)
},
2026-08-18 03:20:09 +03:00
/// Hide reblogs
hideReblogs(id) {
return followUser({
id,
reblogs: false,
credentials: useOAuthStore().token,
}).then((result) => this.updateUserRelationships(result))
2026-08-10 13:52:45 +03:00
},
2026-08-18 03:20:09 +03:00
showReblogs(id) {
return followUser({
id,
reblogs: true,
credentials: useOAuthStore().token,
}).then((result) => this.updateUserRelationships(result))
2026-08-10 13:52:45 +03:00
},
2026-08-18 03:20:09 +03:00
/// Remove follower
removeUserFromFollowers(id) {
return removeUserFromFollowers({ id }).then((result) =>
this.updateUserRelationships(result),
2026-08-10 13:52:45 +03:00
)
},
2026-08-18 03:20:09 +03:00
/// Mute
2026-08-10 13:52:45 +03:00
muteUser(id, expiresIn = 0) {
const predictedRelationship = this.relationships[id] || { id }
2026-08-12 18:01:00 +03:00
predictedRelationship.muting = true
this.updateUserRelationships({
optimism: true,
2026-08-13 01:12:48 +03:00
data: [predictedRelationship],
2026-08-12 18:01:00 +03:00
})
2026-08-10 13:52:45 +03:00
return muteUser({
id,
expiresIn,
credentials: useOAuthStore().token,
2026-08-18 03:20:09 +03:00
}).then((result) => {
this.updateUserRelationships(result)
2026-08-10 13:52:45 +03:00
})
},
2026-08-18 03:20:09 +03:00
muteUsers(data = []) {
return Promise.all(data.map((d) => this.muteUser(d)))
},
2026-08-10 13:52:45 +03:00
unmuteUser(id) {
const predictedRelationship = this.relationships[id] || { id }
predictedRelationship.muting = false
2026-08-12 18:01:00 +03:00
this.updateUserRelationships({
optimism: true,
2026-08-13 01:12:48 +03:00
data: [predictedRelationship],
2026-08-12 18:01:00 +03:00
})
2026-08-10 13:52:45 +03:00
2026-08-18 03:20:09 +03:00
return unmuteUser({ id }).then((result) =>
this.updateUserRelationships(result),
2026-08-10 13:52:45 +03:00
)
},
unmuteUsers(ids = []) {
return Promise.all(ids.map((d) => this.unmuteUser(d)))
},
2026-08-18 03:20:09 +03:00
/// Block
blockUser(id, expiresIn = 0) {
const predictedRelationship = this.relationships[id] || { id }
this.updateUserRelationships({
optimism: true,
data: [predictedRelationship],
})
return blockUser({ id, expiresIn }).then((result) => {
this.updateUserRelationships(result)
2026-08-19 02:03:23 +03:00
const ids = useStatusesStore().wipeUserStatuses(id)
useTimelinesStore().wipeStatuses(ids)
2026-08-10 13:52:45 +03:00
})
},
2026-08-18 03:20:09 +03:00
blockUsers(data = []) {
return Promise.all(data.map((d) => this.blockUser(d)))
},
unblockUser(id) {
return unblockUser({ id }).then((result) => {
this.updateUserRelationships(result)
})
},
unblockUsers(data = []) {
return Promise.all(data.map((d) => this.unblockUser(d)))
},
/// Domain Mute
2026-08-10 13:52:45 +03:00
muteDomain(domain) {
return muteDomain({
domain,
credentials: useOAuthStore().token,
2026-08-18 03:20:09 +03:00
}).then(() => this.currentUser.domainMutes.add(domain))
2026-08-10 13:52:45 +03:00
},
unmuteDomain(domain) {
return unmuteDomain({
domain,
credentials: useOAuthStore().token,
2026-08-18 03:20:09 +03:00
}).then(() => this.currentUser.domainMutes.delete(domain))
2026-08-10 13:52:45 +03:00
},
muteDomains(domains = []) {
return Promise.all(domains.map((domain) => this.muteDomain(domain)))
},
unmuteDomains(domain = []) {
return Promise.all(domain.map((domain) => this.unmuteDomain(domain)))
},
2026-08-18 03:20:09 +03:00
// Login/Logout
async loginUser(accessToken) {
const store = window.vuex
const dispatch =
store?.dispatch ??
(() => {
/* no-op */
}) // for tests
this.loggingIn = true
2026-08-10 13:52:45 +03:00
2026-08-18 03:20:09 +03:00
try {
const { data: user, ...rest } = await verifyCredentials({
credentials: useOAuthStore().token,
})
user.blockIds = new Set()
user.muteIds = new Set()
user.domainMutes = new Set()
this.lastLoginName = user.screen_name
this.currentUser = user
this.users = new Map()
this.usersByName = new Map()
this.usersByURL = new Map()
this.relationships = new Map()
2026-08-18 03:53:29 +03:00
useInterfaceStore().onLogin()
2026-08-18 03:20:09 +03:00
useSyncConfigStore()
.initSyncConfig(user)
.then(() => {
useInterfaceStore()
.applyTheme()
.catch((e) => {
console.error('Error setting theme', e)
})
})
useUserHighlightStore().initUserHighlight(user)
this.addNewUsers({ data: user, ...rest })
useEmojiStore().fetchEmoji()
// Do server-side storage migrations
// Debug snippet to clean up storage and reset migrations
/*
// Reset wordfilter
Object.keys(
useSyncConfigStore().prefsStorage.simple.muteFilters
).forEach(key => {
useSyncConfigStore().unsetSimplePrefAndSave({ path: 'muteFilters.' + key, value: null })
})
// Reset flag to 0 to re-run migrations
useSyncConfigStore().setFlag({ flag: 'configMigration', value: 0 })
/**/
if (user.token) {
// Shoutbox
dispatch('setWsToken', user.token)
dispatch('initializeSocket')
}
// DMs and Home
useNotificationsStore().activate()
useTimelinesStore().activatePersistents()
if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
// Start fetching chats
useChatsStore().startFetching()
}
useListsStore().startFetching()
useBookmarkFoldersStore().startFetching()
if (user.locked) {
dispatch('startFetchingFollowRequests')
}
if (useMergedConfigStore().mergedConfig.useStreamingApi) {
useStreamingStore().initSocket(true)
}
// Start fetching things that don't need to block the UI
useAnnouncementsStore().startFetching()
this.fetchMutes()
dispatch('loadDrafts')
} catch (error) {
console.error(error)
// Authentication failed
this.loggingIn = false
// remove authentication token on client/authentication errors
if ([400, 401, 403, 422].includes(error.statusCode)) {
useOAuthStore().clearToken()
}
if (error.tatusCode === 401) {
throw new Error('Wrong username or password', error)
} else {
throw new Error('An error occurred, please try again', error)
}
} finally {
this.loggingIn = false
}
},
2026-08-10 13:52:45 +03:00
logout() {
const store = window.vuex
const oauth = useOAuthStore()
// Pause fetching
useNotificationsStore().pause()
useTimelinesStore().pauseAll()
// Pause-less stores
useAnnouncementsStore().stopFetching()
useListsStore().stopFetching()
useBookmarkFoldersStore().stopFetching()
store?.dispatch('stopFetchingFollowRequests')
2026-08-10 13:52:45 +03:00
// NOTE: No need to verify the app still exists, because if it doesn't,
// the token will be invalid too
return oauth
.ensureApp()
.then((app) => {
const params = {
app,
instance: useInstanceStore().server,
token: oauth.userToken,
}
return revokeToken(params)
})
.then(() => {
oauth.clearToken()
2026-08-18 03:20:09 +03:00
this.currentUser = null
this.lastLoginName = null
this.users = new Map()
this.usersByName = new Map()
this.usersByURL = new Map()
this.relationships = new Map()
2026-08-14 19:38:37 +03:00
useNotificationsStore().deactivate()
2026-08-13 16:45:44 +03:00
useTimelinesStore().deactivateAll()
// Full reset on logout success
2026-08-10 22:26:22 +03:00
useStatusesStore().resetStatuses()
useTimelinesStore().deactivateAll()
useChatsStore().resetChats()
// Socket is most likely already closed by server
if (
useMergedConfigStore().mergedConfig.useStreamingApi
&& useStreamingStore().state !== WSConnectionStatus.CLOSED
) {
2026-08-18 03:20:09 +03:00
useStreamingStore().stopSocket()
}
2026-08-10 13:52:45 +03:00
Cookies.remove('__Host-pleroma_key', { path: '/' })
2026-08-18 03:20:09 +03:00
useInterfaceStore().onLogout()
2026-08-10 13:52:45 +03:00
})
.catch((e) => {
useInterfaceStore().pushGlobalNotice({
messageKey: 'user.logout_failure',
messageArgs: {
error: e,
},
level: 'error',
})
console.error('Logout error!', e)
useAnnouncementsStore().startFetching()
useListsStore().startFetching()
useBookmarkFoldersStore().startFetching()
store?.dispatch('startFetchingFollowRequests')
})
.finally(() => {
useNotificationsStore().resume()
useTimelinesStore().resumeAll()
})
2026-08-10 13:52:45 +03:00
},
},
2026-08-10 15:48:27 +03:00
persist: {
2026-08-10 16:22:33 +03:00
paths: ['lastLoginName'],
2026-08-10 15:48:27 +03:00
},
2026-08-10 13:52:45 +03:00
})