fix(push): bump SW to v12, улучшен openOrFocusChat и notificationclick
- openOrFocusChat: includeUncontrolled=true, логирование, try/catch, fallback на clients.openWindow. - notificationclick: логирование data, fallback построения URL из вложенного data.data. - markNotificationRead: поддержка type/messageId/conversationId на верхнем уровне и во вложенном data. - Bump CACHE_NAME до v12 для принудительного обновления SW.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
const CACHE_NAME = 'iistwin-pwa-v11';
|
const CACHE_NAME = 'iistwin-pwa-v12';
|
||||||
const STATIC_ASSETS = [
|
const STATIC_ASSETS = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/index.html',
|
||||||
@@ -568,54 +568,73 @@ self.addEventListener('push', (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function openOrFocusChat(url) {
|
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 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).
|
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) {
|
for (const client of clientList) {
|
||||||
try {
|
try {
|
||||||
const clientPath = new URL(client.url).pathname;
|
if (client.url === absoluteUrl && 'focus' in client) {
|
||||||
if (clientPath === targetPath && 'focus' in client) {
|
console.log('[SW] focusing existing client with same URL');
|
||||||
await client.navigate(absoluteUrl);
|
|
||||||
return await client.focus();
|
return await client.focus();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[SW] Failed to navigate matching client:', err);
|
console.error('[SW] focus same-url client error:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise focus any controlled same-origin client and navigate it.
|
// Затем пытаемся навигировать любой доступный клиент.
|
||||||
for (const client of clientList) {
|
for (const client of clientList) {
|
||||||
try {
|
try {
|
||||||
if ('focus' in client) {
|
if ('focus' in client) {
|
||||||
|
console.log('[SW] navigating client to', absoluteUrl);
|
||||||
await client.navigate(absoluteUrl);
|
await client.navigate(absoluteUrl);
|
||||||
return await client.focus();
|
return await client.focus();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[SW] Failed to navigate client:', err);
|
console.error('[SW] navigate client error:', 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) {
|
async function markNotificationRead(data) {
|
||||||
try {
|
try {
|
||||||
if (data?.type === 'chat' && data?.messageId) {
|
// Поддерживаем оба формата: type на верхнем уровне и вложенный data.type
|
||||||
await fetch(`/api/messages/${data.messageId}/read`, {
|
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',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
console.log('[SW] Marked chat message as read:', data.messageId);
|
console.log('[SW] Marked chat message as read:', messageId);
|
||||||
} else if (data?.type === 'messenger' && data?.conversationId) {
|
} else if (type === 'messenger' && conversationId) {
|
||||||
await fetch(`/api/messenger/conversations/${data.conversationId}/read`, {
|
await fetch(`/api/messenger/conversations/${conversationId}/read`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
console.log('[SW] Marked messenger conversation as read:', data.conversationId);
|
console.log('[SW] Marked messenger conversation as read:', conversationId);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[SW] Failed to mark notification as read:', err);
|
console.error('[SW] Failed to mark notification as read:', err);
|
||||||
@@ -623,18 +642,31 @@ async function markNotificationRead(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('notificationclick', (event) => {
|
self.addEventListener('notificationclick', (event) => {
|
||||||
console.log('[SW] Notification clicked', event.action);
|
console.log('[SW] Notification clicked, action:', event.action);
|
||||||
event.notification.close();
|
event.notification.close();
|
||||||
|
|
||||||
const data = event.notification.data || {};
|
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') {
|
if (event.action === 'read') {
|
||||||
// "Прочитано": помечаем прочитанным и всё равно открываем задачу/чат, как ожидает пользователь.
|
// "Прочитано": помечаем прочитанным и всё равно открываем задачу/чат, как ожидает пользователь.
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
Promise.all([
|
Promise.all([
|
||||||
navigator.clearAppBadge ? navigator.clearAppBadge() : Promise.resolve(),
|
navigator.clearAppBadge ? navigator.clearAppBadge() : Promise.resolve(),
|
||||||
markNotificationRead(data),
|
markNotificationRead(data.data || data),
|
||||||
openOrFocusChat(urlToOpen),
|
openOrFocusChat(urlToOpen),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user