diff --git a/client/public/sw.js b/client/public/sw.js index d867b3d..735dffb 100644 --- a/client/public/sw.js +++ b/client/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'iistwin-pwa-v11'; +const CACHE_NAME = 'iistwin-pwa-v12'; const STATIC_ASSETS = [ '/', '/index.html', @@ -568,54 +568,73 @@ self.addEventListener('push', (event) => { }); async function openOrFocusChat(url) { - // Only controlled clients can be navigated/focused reliably from a SW. - const clientList = await clients.matchAll({ type: 'window', includeUncontrolled: false }); const absoluteUrl = new URL(url, self.location.origin).href; - const targetPath = new URL(absoluteUrl).pathname; + console.log('[SW] openOrFocusChat:', absoluteUrl); - // Prefer a controlled client that already has the same path (ignoring query string). - for (const client of clientList) { - try { - const clientPath = new URL(client.url).pathname; - if (clientPath === targetPath && 'focus' in client) { - await client.navigate(absoluteUrl); - return await client.focus(); + try { + // Ищем любые окна (включая неподконтрольные) — focus работает и на них, + // а navigate может не сработать на uncontrolled, поэтому оборачиваем в try/catch. + const clientList = await clients.matchAll({ type: 'window', includeUncontrolled: true }); + console.log('[SW] clients found:', clientList.length); + + // Сначала ищем клиент с уже открытым этим URL — просто фокусируем. + for (const client of clientList) { + try { + if (client.url === absoluteUrl && 'focus' in client) { + console.log('[SW] focusing existing client with same URL'); + return await client.focus(); + } + } catch (err) { + console.error('[SW] focus same-url client error:', err); } - } catch (err) { - console.error('[SW] Failed to navigate matching client:', err); } - } - // Otherwise focus any controlled same-origin client and navigate it. - for (const client of clientList) { - try { - if ('focus' in client) { - await client.navigate(absoluteUrl); - return await client.focus(); + // Затем пытаемся навигировать любой доступный клиент. + for (const client of clientList) { + try { + if ('focus' in client) { + console.log('[SW] navigating client to', absoluteUrl); + await client.navigate(absoluteUrl); + return await client.focus(); + } + } catch (err) { + console.error('[SW] navigate client error:', err); } - } catch (err) { - console.error('[SW] Failed to navigate client:', err); } - } - // No usable client — open a new window/tab. This is always allowed in notificationclick. - return await clients.openWindow(absoluteUrl); + // Нет подходящих клиентов — открываем новое окно/вкладку. + console.log('[SW] opening new window'); + const newClient = await clients.openWindow(absoluteUrl); + if (newClient && 'focus' in newClient) { + return await newClient.focus(); + } + return newClient; + } catch (err) { + console.error('[SW] openOrFocusChat fatal error:', err); + // Последний fallback — всё равно пытаемся открыть окно. + return clients.openWindow(absoluteUrl); + } } async function markNotificationRead(data) { try { - if (data?.type === 'chat' && data?.messageId) { - await fetch(`/api/messages/${data.messageId}/read`, { + // Поддерживаем оба формата: type на верхнем уровне и вложенный data.type + const type = data?.type || data?.data?.type; + const messageId = data?.messageId || data?.data?.messageId; + const conversationId = data?.conversationId || data?.data?.conversationId; + + if (type === 'chat' && messageId) { + await fetch(`/api/messages/${messageId}/read`, { method: 'POST', credentials: 'include', }); - console.log('[SW] Marked chat message as read:', data.messageId); - } else if (data?.type === 'messenger' && data?.conversationId) { - await fetch(`/api/messenger/conversations/${data.conversationId}/read`, { + console.log('[SW] Marked chat message as read:', messageId); + } else if (type === 'messenger' && conversationId) { + await fetch(`/api/messenger/conversations/${conversationId}/read`, { method: 'POST', credentials: 'include', }); - console.log('[SW] Marked messenger conversation as read:', data.conversationId); + console.log('[SW] Marked messenger conversation as read:', conversationId); } } catch (err) { console.error('[SW] Failed to mark notification as read:', err); @@ -623,18 +642,31 @@ async function markNotificationRead(data) { } self.addEventListener('notificationclick', (event) => { - console.log('[SW] Notification clicked', event.action); + console.log('[SW] Notification clicked, action:', event.action); event.notification.close(); const data = event.notification.data || {}; - const urlToOpen = data.url || '/'; + console.log('[SW] Notification data:', JSON.stringify(data)); + + // Fallback: если url отсутствует, пытаемся восстановить его из вложенного data. + let urlToOpen = data.url || '/'; + if ((!data.url || data.url === '/') && data.data) { + const nested = data.data; + if (nested.type === 'chat' && nested.taskId && nested.formId) { + urlToOpen = `/forms/${nested.formId}/tasks/${nested.taskId}${nested.isChat === 'true' ? '?tab=chat' : ''}`; + console.log('[SW] Rebuilt url from nested data:', urlToOpen); + } else if (nested.type === 'messenger' && nested.conversationId) { + urlToOpen = `/chat/${nested.conversationId}`; + console.log('[SW] Rebuilt url from nested data:', urlToOpen); + } + } if (event.action === 'read') { // "Прочитано": помечаем прочитанным и всё равно открываем задачу/чат, как ожидает пользователь. event.waitUntil( Promise.all([ navigator.clearAppBadge ? navigator.clearAppBadge() : Promise.resolve(), - markNotificationRead(data), + markNotificationRead(data.data || data), openOrFocusChat(urlToOpen), ]) );