diff --git a/src/stores/notification.js b/src/stores/notification.js index 7287babf..7c302a51 100644 --- a/src/stores/notification.js +++ b/src/stores/notification.js @@ -10,6 +10,7 @@ import { checkCanInvite, displayLocation, escapeTag, + executeWithBackoff, extractFileId, extractFileVersion, getUserMemo, @@ -403,6 +404,71 @@ export const useNotificationStore = defineStore('Notification', () => { database.seenNotificationV2(notificationId); } + const seeQueue = []; + const seenIds = new Set(); + let seeProcessing = false; + const SEE_CONCURRENCY = 5; + + async function processSeeQueue() { + if (seeProcessing) return; + seeProcessing = true; + const worker = async () => { + let item; + while ((item = seeQueue.shift())) { + const { id, version } = item; + try { + await executeWithBackoff( + async () => { + if (version >= 2) { + const args = + await notificationRequest.seeNotificationV2( + { notificationId: id } + ); + handleNotificationV2Update({ + params: { notificationId: id }, + json: { ...args.json, seen: true } + }); + } else { + await notificationRequest.seeNotification({ + notificationId: id + }); + handleNotificationSee(id); + } + }, + { + maxRetries: 3, + baseDelay: 1000, + shouldRetry: (err) => + err?.status === 429 || + (err?.message || '').includes('429') + } + ); + } catch (err) { + console.warn('Failed to mark notification as seen:', id); + if (version >= 2) { + handleNotificationV2Hide(id); + } + } + } + }; + await Promise.all( + Array.from({ length: SEE_CONCURRENCY }, () => worker()) + ); + seeProcessing = false; + } + + /** + * Queue a notification to be marked as seen. + * @param {string} notificationId + * @param {number} [version=1] + */ + function queueMarkAsSeen(notificationId, version = 1) { + if (seenIds.has(notificationId)) return; + seenIds.add(notificationId); + seeQueue.push({ id: notificationId, version }); + processSeeQueue(); + } + function handleNotificationAccept(args) { let ref; const array = notificationTable.value.data; @@ -2754,6 +2820,7 @@ export const useNotificationStore = defineStore('Notification', () => { hasUnseenNotifications, getNotificationCategory, isNotificationExpired, - openNotificationLink + openNotificationLink, + queueMarkAsSeen }; }); diff --git a/src/views/Sidebar/components/NotificationItem.vue b/src/views/Sidebar/components/NotificationItem.vue index 40e2cb19..31f5771f 100644 --- a/src/views/Sidebar/components/NotificationItem.vue +++ b/src/views/Sidebar/components/NotificationItem.vue @@ -249,7 +249,6 @@ import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; import { TooltipWrapper } from '@/components/ui/tooltip'; - import { notificationRequest } from '@/api'; import { storeToRefs } from 'pinia'; import { useI18n } from 'vue-i18n'; @@ -273,7 +272,7 @@ const notificationStore = useNotificationStore(); const { lastLocation } = storeToRefs(useLocationStore()); const { isGameRunning } = storeToRefs(useGameStore()); - const { openNotificationLink, isNotificationExpired, handleNotificationV2Hide } = useNotificationStore(); + const { openNotificationLink, isNotificationExpired } = useNotificationStore(); const senderName = computed(() => { const n = props.notification; @@ -479,34 +478,11 @@ } onBeforeUnmount(() => { - // Mark as seen + // Mark as seen (queued to avoid 429 rate-limiting) if (isNotificationExpired(props.notification) || isSeen.value) { return; } - const params = { notificationId: props.notification.id }; - if (!props.notification.version || props.notification.version < 2) { - notificationRequest.seeNotification({ notificationId: props.notification.id }).then((args) => { - console.log('Marked notification-v1 as seen:', args.json); - notificationStore.handleNotificationSee(props.notification.id); - }); - return; - } - notificationRequest - .seeNotificationV2(params) - .then((args) => { - console.log('Marked notification-v2 as seen:', args.json); - const newArgs = { - params, - json: { - ...args.json, - seen: true - } - }; - notificationStore.handleNotificationV2Update(newArgs); - }) - .catch((err) => { - console.error('Failed to mark notification-v2 as seen:', err); - handleNotificationV2Hide(props.notification.id); - }); + const version = props.notification.version || 1; + notificationStore.queueMarkAsSeen(props.notification.id, version); });