fix(offline): presence-вызовы не попадают в офлайн-очередь и молча дропаются из конфликтов
В PWA накапливались десятки конфликтов «/api/devices/active-chat»: эфемерные presence-вызовы (активный чат, heartbeat) при сетевых сбоях ставились в очередь, при реплее ловили 4xx и становились конфликтами, требующими ручной разборки. Реплеить их бессмысленно — они устаревают мгновенно. - isQueueable: /api/devices/active-chat и /api/devices/heartbeat больше не ставятся в очередь (как read receipts); - isSafeToDrop: те же URL молча дропаются при 4xx; - уже помеченные конфликтами presence-записи авто-синк дропает молча — накопленные десятки очистятся при следующей синхронизации.
This commit is contained in:
@@ -113,7 +113,11 @@ async function replayRequest(item: OfflineQueueItem): Promise<Response> {
|
||||
}
|
||||
|
||||
function isSafeToDrop(item: OfflineQueueItem): boolean {
|
||||
return item.method === 'POST' && /\/api\/messages\/\d+\/read$/.test(item.url);
|
||||
if (item.method === 'POST' && /\/api\/messages\/\d+\/read$/.test(item.url)) return true;
|
||||
// Эфемерные presence-вызовы, случайно попавшие в очередь до исключения из
|
||||
// isQueueable (см. queryClient.ts) — реплеить бессмысленно, дропаем молча
|
||||
if (item.url.includes('/api/devices/active-chat') || item.url.includes('/api/devices/heartbeat')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Module-level lock so only one sync runs at a time across all hook instances.
|
||||
@@ -443,9 +447,16 @@ export function useOfflineSync(userId?: number): UseOfflineSyncReturn {
|
||||
const newConflicts: OfflineQueueItem[] = [];
|
||||
|
||||
for (const item of itemsToReplay) {
|
||||
// Items already marked as conflicted need manual resolution — skip during auto-sync
|
||||
// Items already marked as conflicted need manual resolution — skip during auto-sync.
|
||||
// Исключение — некритичные эфемерные запросы (presence, read receipts):
|
||||
// дропаем молча даже из конфликтов, пользователю незачем разбирать их вручную.
|
||||
if (item.conflictStatus === 'conflict') {
|
||||
newConflicts.push(item);
|
||||
if (isSafeToDrop(item)) {
|
||||
await remove(item.id);
|
||||
window.dispatchEvent(new CustomEvent('offline-queue-updated'));
|
||||
} else {
|
||||
newConflicts.push(item);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ function isQueueable(method: string, url: string, data?: unknown): boolean {
|
||||
if (data instanceof FormData) return false;
|
||||
// Read-receipt requests are non-critical — don't queue them offline
|
||||
if (method.toUpperCase() === 'POST' && /\/api\/messages\/\d+\/read$/.test(url)) return false;
|
||||
// Эфемерные presence-вызовы (активный чат, heartbeat) бессмысленно реплеить —
|
||||
// они устаревают мгновенно; иначе копились сотнями в конфликтах
|
||||
if (url.includes('/api/devices/active-chat') || url.includes('/api/devices/heartbeat')) return false;
|
||||
return !OFFLINE_QUEUE_EXCLUDED.some((prefix) => url.includes(prefix));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user