diff --git a/client/public/sw.js b/client/public/sw.js index 10e1d8e..d867b3d 100644 --- a/client/public/sw.js +++ b/client/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'iistwin-pwa-v10'; +const CACHE_NAME = 'iistwin-pwa-v11'; const STATIC_ASSETS = [ '/', '/index.html', @@ -568,23 +568,38 @@ self.addEventListener('push', (event) => { }); async function openOrFocusChat(url) { - const clientList = await clients.matchAll({ type: 'window', includeUncontrolled: true }); - // Prefer a client that already has the same path (ignoring query string). - const targetPath = url.split('?')[0]; + // 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; + + // Prefer a controlled client that already has the same path (ignoring query string). for (const client of clientList) { - if (client.url.includes(self.location.origin) && client.url.split('?')[0] === targetPath && 'focus' in client) { - await client.navigate(url); - return client.focus(); + try { + const clientPath = new URL(client.url).pathname; + if (clientPath === targetPath && 'focus' in client) { + await client.navigate(absoluteUrl); + return await client.focus(); + } + } catch (err) { + console.error('[SW] Failed to navigate matching client:', err); } } - // Otherwise focus any same-origin client and navigate it. + + // Otherwise focus any controlled same-origin client and navigate it. for (const client of clientList) { - if (client.url.includes(self.location.origin) && 'focus' in client) { - await client.navigate(url); - return client.focus(); + try { + if ('focus' in client) { + await client.navigate(absoluteUrl); + return await client.focus(); + } + } catch (err) { + console.error('[SW] Failed to navigate client:', err); } } - return clients.openWindow(url); + + // No usable client — open a new window/tab. This is always allowed in notificationclick. + return await clients.openWindow(absoluteUrl); } async function markNotificationRead(data) { @@ -615,11 +630,18 @@ self.addEventListener('notificationclick', (event) => { const urlToOpen = data.url || '/'; if (event.action === 'read') { - event.waitUntil(markNotificationRead(data)); + // "Прочитано": помечаем прочитанным и всё равно открываем задачу/чат, как ожидает пользователь. + event.waitUntil( + Promise.all([ + navigator.clearAppBadge ? navigator.clearAppBadge() : Promise.resolve(), + markNotificationRead(data), + openOrFocusChat(urlToOpen), + ]) + ); return; } - // 'reply' or body click: open the chat and focus an existing tab if possible. + // Body click or 'reply' action: open the linked task/chat and focus an existing tab if possible. event.waitUntil( Promise.all([ navigator.clearAppBadge ? navigator.clearAppBadge() : Promise.resolve(),