diff --git a/src/lib/push_notifications_plugin.js b/src/lib/push_notifications_plugin.js index b3f6885d7..ad546922d 100644 --- a/src/lib/push_notifications_plugin.js +++ b/src/lib/push_notifications_plugin.js @@ -37,9 +37,7 @@ export const piniaPushNotificationsPlugin = ({ store }) => { if (store.$id === 'interface') { if (actionName === 'setNotificationPermission') { permissionGranted = args[0] === 'granted' - } else if (actionName === 'setLoginStatus') { - user = args[0] - } else { + } else if (actionName !== 'onLogin' && actionName !== 'onLogout') { return } } else if (store.$id === 'sync_config') { diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js index e99958110..bbe74ce09 100644 --- a/src/services/chat_utils/chat_utils.js +++ b/src/services/chat_utils/chat_utils.js @@ -3,8 +3,11 @@ import { showDesktopNotification } from '../desktop_notification_utils/desktop_n import { useUsersStore } from 'src/stores/users.js' export const maybeShowChatNotification = (chat) => { + // No messages if (!chat.lastMessage) return + // No unreads to display if (chat.unread === 0) return + // Don't notify on outgoing message (shouldn't happen with condition above) if (useUsersStore().currentUser.id === chat.lastMessage.account_id) return const opts = { diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index f3b845f61..1e47cce2e 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -430,7 +430,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) resultUserIds.data.forEach((userId) => { - useStatusesStore().wipeUserStatuses(status.user.id) + useStatusesStore().wipeUserStatuses(userId) // Users are technically never deleted, just deactivated // so there's no real need to delete them from store. }) diff --git a/src/stores/chats.js b/src/stores/chats.js index 86ea310ce..6fcfbcd1e 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -40,8 +40,8 @@ export const useChatsStore = defineStore('chats', { useStreamingStore().addSubscriber(socket) }, startFetching() { - this.fetcher = () => promiseInterval(() => this.fetchChats(), 5000) - this.fetcher() + this.fetcher = promiseInterval(() => this.fetchChats(), 5000) + this.fetchChats() }, stopFetching() { this.fetcher?.stop() @@ -56,8 +56,6 @@ export const useChatsStore = defineStore('chats', { }, resetChats() { this.data = new Map() - this.stopFetching() - this.startFetching() }, addNewChats(result) { useUsersStore().addNewUsers({ @@ -79,13 +77,15 @@ export const useChatsStore = defineStore('chats', { updateChat(updatedChat) { const chat = this.data.get(updatedChat.id) if (chat) { + const isNewMessage = chat.lastMessage !== updatedChat.lastMessage chat.lastMessage = updatedChat.lastMessage chat.unread = updatedChat.unread chat.updated_at = updatedChat.updated_at + if (isNewMessage) maybeShowChatNotification(chat) } else { this.data.set(updatedChat.id, updatedChat) + maybeShowChatNotification(updatedChat) } - maybeShowChatNotification(chat ?? updatedChat) }, deleteChat(id) { this.data.delete(id) diff --git a/src/stores/fetchers/timeline_fetcher.js b/src/stores/fetchers/timeline_fetcher.js index 54250318f..5737befef 100644 --- a/src/stores/fetchers/timeline_fetcher.js +++ b/src/stores/fetchers/timeline_fetcher.js @@ -46,7 +46,7 @@ const timelineFetcher = (timeline, argument, credentials) => { } args.withMuted = !hideMutedPosts - if (loggedIn && REPLY_VISIBILITY_TIMELINES.has(timeline)) { + if (loggedIn && REPLY_VISIBILITY_TIMELINES.has(timeline.name)) { args.replyVisibility = replyVisibility } diff --git a/src/stores/notifications.js b/src/stores/notifications.js index 4f18ccc21..c69e9c164 100644 --- a/src/stores/notifications.js +++ b/src/stores/notifications.js @@ -22,17 +22,41 @@ import { import { isStatusNotification } from 'src/services/notification_utils/notification_utils_sw.js' export const defaultState = () => ({ + // Prevents desktop notification spam on startup desktopNotificationSilence: true, + + // Pagination maxId: '', minId: '', + + // Order data: [], + + // TODO: Implement! + // Useful for making notification as seen + // when interacting with status statusNotificationRelations: new WeakMap(), + + // ID to Object notification idStore: new Map(), - statusIdStore: new Set(), + + // Reference to WS subscriber socket: null, + + // Indicates whether notifications receive push updates streaming: false, + + // Indicates whether notifications are SUPPOSED to be fetching + // this is partiualrly useful for when pausing/resuming. I.e. + // whether we need to start fetching again if timeline was resumed. fetching: true, + + // Reference to fetcher, used for polling for new notifications + // and manually fetching old notifications fetcher: null, + + // Whether notifications fetcher has been paused - it stops fetching + // (but still receives pushes!) paused: false, }) diff --git a/src/stores/reports.js b/src/stores/reports.js index 2d9819442..0b0e25380 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -19,8 +19,10 @@ export const useReportsStore = defineStore('reports', { actions: { openUserReportingModal({ userId, statusIds = [] }) { const preTickedIds = new Set(statusIds) - // There shouldn't be a case where this is undefined - const userAllStatusesIds = useStatusesStore().statusesPerUser.get(userId) + // There could be a case (i.e. user is only ever mentioned in someone else's post -> user popover) + // where user has no known posts + const userAllStatusesIds = + useStatusesStore().statusesPerUser.get(userId) ?? new Set() // Set constructor should take care of duplicated IDs and order, // later duplicated IDs will be dropped in favor of earlier const sortedIds = new Set([...preTickedIds, ...userAllStatusesIds]) diff --git a/src/stores/statuses.js b/src/stores/statuses.js index 8a5938211..ceb51ceb6 100644 --- a/src/stores/statuses.js +++ b/src/stores/statuses.js @@ -534,7 +534,7 @@ export const useStatusesStore = defineStore('statuses', { // For when blocking a user wipeUserStatuses(userId) { - const removed = this.statusesPerUser.get(userId) + const removed = this.statusesPerUser.get(userId) ?? new Set() removed.forEach((statusId) => { const status = this.allStatuses.get(statusId) this.allStatuses.delete(statusId) diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 562bec21d..bdd49d05c 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -8,18 +8,49 @@ import { TIMELINE_STREAM_MAP, useStreamingStore } from 'src/stores/streaming.js' const emptyTl = (name, argument = null) => { const result = { + // Name of the timeline. Useful for debugging and logging name, + + // Order of statuses, important for timelines that + // have different ordering, i.e. bookmarks and favorites order: [], + + // All statuses belonging to the timeline statusIds: new Set(), + + // Statuses shown to user visibleStatusIds: new Set(), + + // Number of statuses not shown yet newStatusCount: 0, + + // Pagination maxId: '', minId: '', + + // Indicates whether timeline receives push updates streaming: false, + + // Indicates whether timeline is SUPPOSED to be fetching + // this is partiualrly useful for when pausing/resuming + // timeline. I.e. whether we need to start fetching again + // if timeline was resumed. fetching: false, + + // Indicates that in recent poll update we've hit more than or + // equal to 20 statuses and most likely missed some statuses + // between polls reloadNeeded: false, + + // Reference to fetcher, used for polling for new statuses and + // manually fetching old statuses fetcher: null, + + // Reference to WS subscriber socket: null, + + // Whether the timeline has been paused - it stops fetching + // (but still receives pushes!) paused: false, } @@ -316,6 +347,14 @@ export const useTimelinesStore = defineStore('timelines', { reason, ) return + } else if (timeline.paused) { + console.debug( + '[Timelines] Deactivating paused timeline', + timelineName, + 'Reason:', + reason, + ) + timeline.fetching = false } else { timeline.fetcher.stopFetching() console.debug( diff --git a/src/stores/users.js b/src/stores/users.js index f41f75aa2..1c32ab679 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -50,7 +50,6 @@ import { promiseInterval } from 'src/services/promise_interval/promise_interval. export const useUsersStore = defineStore('users', { state: () => ({ loggingIn: false, - lastLoginName: null, currentUser: null, users: new Map(), usersByName: new Map(), @@ -616,7 +615,6 @@ export const useUsersStore = defineStore('users', { user.muteIds = new Set() user.domainMutes = new Set() - this.lastLoginName = user.screen_name useTimelinesStore().deactivateAll() useStatusesStore().resetStatuses() @@ -739,13 +737,13 @@ export const useUsersStore = defineStore('users', { oauth.clearToken() this.currentUser = null - this.lastLoginName = null useNotificationsStore().deactivate() // Full reset on logout success useTimelinesStore().deactivateAll() useStatusesStore().resetStatuses() + useChatsStore().stopFetching() useChatsStore().resetChats() this.users = new Map() @@ -777,6 +775,7 @@ export const useUsersStore = defineStore('users', { useAnnouncementsStore().startFetching() useListsStore().startFetching() useBookmarkFoldersStore().startFetching() + useChatsStore().startFetching() store?.dispatch('startFetchingFollowRequests') }) .finally(() => { @@ -785,7 +784,4 @@ export const useUsersStore = defineStore('users', { }) }, }, - persist: { - paths: ['lastLoginName'], - }, }) diff --git a/test/unit/specs/stores/users.spec.js b/test/unit/specs/stores/users.spec.js index 285359c62..2709d680a 100644 --- a/test/unit/specs/stores/users.spec.js +++ b/test/unit/specs/stores/users.spec.js @@ -679,7 +679,6 @@ describe('Users store', () => { expect(store.usersByName).to.have.length(1) expect(store.usersByURL).to.have.length(1) expect(store.relationships).to.have.length(0) - expect(store.lastLoginName).to.eql(userScreenName) spies.forEach((spy, index) => { expect(spy, `Spy ${index} has failed`).to.have.been.called }) @@ -783,7 +782,6 @@ describe('Users store', () => { expect(store.loggedIn).to.eql(true) await store.logout() expect(store.loggedIn).to.eql(false) - expect(store.lastLoginName).to.eql(null) expect(revokeApi).to.have.been.called expect(store.users).to.have.length(0) expect(store.usersByName).to.have.length(0)