feat: Implement Web Push notifications with VAPID
- Split notification sending into separate WebSocket and Web Push channels - Add Web Push support for offline users - Configure VAPID keys for push authentication - Add 'aud' field to VAPID claims for WNS compatibility - Optimize subscription sync - only POST when endpoint changes - Remove debug logs and minimize server logging - Update Service Worker with improved error handling - Add Service-Worker-Allowed header for proper scope Web Push now works independently from WebSocket, enabling notifications when browser is closed (requires HTTPS in production)
This commit is contained in:
parent
e5503a1490
commit
44488cb40e
@ -120,7 +120,7 @@ def notify_specific_recipients(sender, instance, action, pk_set, **kwargs):
|
||||
)
|
||||
|
||||
if settings.send_web:
|
||||
NotificationService.send_web_notification(notification)
|
||||
NotificationService.send_web_socket(notification)
|
||||
notification.sent_web = True
|
||||
notification.save(update_fields=['sent_web'])
|
||||
except Exception as e:
|
||||
@ -256,7 +256,7 @@ def notify_department_employees(sender, instance, action, pk_set, **kwargs):
|
||||
)
|
||||
|
||||
if settings_obj.send_web:
|
||||
NotificationService.send_web_notification(notification)
|
||||
NotificationService.send_web_socket(notification)
|
||||
notification.sent_web = True
|
||||
notification.save(update_fields=['sent_web'])
|
||||
except Exception as e:
|
||||
@ -397,7 +397,7 @@ def notify_all_employees(document):
|
||||
|
||||
# Только веб
|
||||
if settings.send_web:
|
||||
NotificationService.send_web_notification(notification)
|
||||
NotificationService.send_web_socket(notification)
|
||||
notification.sent_web = True
|
||||
notification.save(update_fields=['sent_web'])
|
||||
except Exception as e:
|
||||
|
||||
@ -450,13 +450,12 @@ TELEGRAM_BOT_USERNAME = os.getenv("TELEGRAM_BOT_USERNAME", "eusrr_bot")
|
||||
|
||||
# Web Push Notifications (VAPID)
|
||||
# Ключи для авторизации push-уведомлений
|
||||
# Для генерации новых ключей: python -c "from py_vapid import Vapid; v = Vapid(); v.generate_keys(); v.save_key('private_key.pem'); v.save_public_key('public_key.pem')"
|
||||
VAPID_PUBLIC_KEY = os.getenv(
|
||||
"VAPID_PUBLIC_KEY",
|
||||
"BG3WGJZH3bCPtZ1yRjXywfGsHoRT4MZ7npk25twmBireG_Di5-1FBn1ChblWCDicienEr07y4k7R7_A5IjbfjRA="
|
||||
"BMTitZy9r4ygYJBgGdaZuCkb7rwR7iHLJv0DkNpaJOLESotONETWtZAQxnJVU_Yo9v-iYo-7dWEeF0VEjMcGMkQ"
|
||||
)
|
||||
VAPID_PRIVATE_KEY = os.getenv(
|
||||
"VAPID_PRIVATE_KEY",
|
||||
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgIYJXs5gLYLtLpWO0cr6prv4jHJT28_hvG1BFyjcIMZKhRANCAARt1hiWR92wj7WdckY18sHxrB6EU-DGe56ZNubcJgYq3hvw4uftRQZ9QoW5Vgg4nInpxK9O8uJO0e_wOSI2340Q"
|
||||
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCNnpupg3xbtFUiOSUZ6L7s6puxuEjzR73kTL7v8bMvKhRANCAATE4rWcva-MoGCQYBnWmbgpG-68Ee4hyyb9A5DaWiTixEqLTjRE1rWQEMZyVVP2KPb_omKPu3VhHhdFRIzHBjJE"
|
||||
)
|
||||
VAPID_ADMIN_EMAIL = os.getenv("VAPID_ADMIN_EMAIL", "admin@example.com")
|
||||
VAPID_ADMIN_EMAIL = os.getenv("VAPID_ADMIN_EMAIL", "robotail-info@yandex.ru")
|
||||
@ -6,14 +6,21 @@ from django.http import FileResponse
|
||||
import os
|
||||
|
||||
def serve_service_worker(request):
|
||||
"""Обслуживает Service Worker с правильным content-type"""
|
||||
"""Обслуживает Service Worker с правильным content-type и заголовками"""
|
||||
sw_path = os.path.join(settings.BASE_DIR, 'sw.js')
|
||||
try:
|
||||
return FileResponse(
|
||||
response = FileResponse(
|
||||
open(sw_path, 'rb'),
|
||||
content_type='application/javascript',
|
||||
content_type='application/javascript; charset=utf-8',
|
||||
status=200
|
||||
)
|
||||
# Отключаем кэширование для sw.js (всегда получаем свежую версию)
|
||||
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||
response['Pragma'] = 'no-cache'
|
||||
response['Expires'] = '0'
|
||||
# Service-Worker-Allowed позволяет контролировать весь домен
|
||||
response['Service-Worker-Allowed'] = '/'
|
||||
return response
|
||||
except Exception as e:
|
||||
from django.http import HttpResponse
|
||||
return HttpResponse(f"Service Worker not found: {e}", status=500)
|
||||
|
||||
@ -480,10 +480,9 @@ def subscribe_push(request):
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[WebPush] {'Created' if created else 'Updated'} subscription for user {request.user.id} "
|
||||
f"(endpoint: {endpoint[:50]}...)"
|
||||
)
|
||||
# Логируем только создание новой подписки, не обновление
|
||||
if created:
|
||||
logger.info(f"[WebPush] New subscription for user {request.user.id}")
|
||||
|
||||
return Response({
|
||||
'status': 'success',
|
||||
|
||||
@ -161,13 +161,14 @@ class NotificationService:
|
||||
f"{'✅' if settings.send_telegram else '❌'} Telegram"
|
||||
)
|
||||
|
||||
# Отправить на веб (WebSocket)
|
||||
# Отправить на веб (WebSocket) - для онлайн пользователей
|
||||
if settings.send_web:
|
||||
logger.info(
|
||||
f"[NotificationService.send_notification] 🌐 Отправка через WebSocket..."
|
||||
)
|
||||
try:
|
||||
NotificationService.send_web_notification(notification)
|
||||
# Отправляем WebSocket только для онлайн уведомлений (не push)
|
||||
NotificationService.send_web_socket(notification)
|
||||
notification.sent_web = True
|
||||
logger.info(
|
||||
f"[NotificationService.send_notification] ✅ WebSocket: успешно отправлено"
|
||||
@ -181,6 +182,29 @@ class NotificationService:
|
||||
logger.info(
|
||||
f"[NotificationService.send_notification] ⏭️ WebSocket: канал отключен"
|
||||
)
|
||||
|
||||
# Отправить Web Push (для offline пользователей)
|
||||
# ВАЖНО: отправляем независимо от send_web настройки,
|
||||
# если есть активные push-подписки
|
||||
if settings.send_web:
|
||||
logger.info(
|
||||
f"[NotificationService.send_notification] 📲 Отправка Web Push (offline)..."
|
||||
)
|
||||
try:
|
||||
push_count = NotificationService.send_web_push_notification(notification)
|
||||
if push_count > 0:
|
||||
logger.info(
|
||||
f"[NotificationService.send_notification] ✅ Web Push: отправлено на {push_count} устройств"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[NotificationService.send_notification] ❌ Web Push: ошибка - {e}",
|
||||
exc_info=True
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f"[NotificationService.send_notification] ⏭️ Web Push: канал отключен"
|
||||
)
|
||||
|
||||
# Отправить на email
|
||||
if settings.send_email:
|
||||
@ -312,10 +336,13 @@ class NotificationService:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def send_web_notification(notification: Notification):
|
||||
"""Отправить уведомление через WebSocket"""
|
||||
def send_web_socket(notification: Notification):
|
||||
"""
|
||||
Отправить уведомление через WebSocket (только для онлайн пользователей).
|
||||
Переименовано из send_web_notification для ясности.
|
||||
"""
|
||||
logger.info(
|
||||
f"[NotificationService.send_web_notification] НАЧАЛО: "
|
||||
f"[NotificationService.send_web_socket] НАЧАЛО: "
|
||||
f"notification_id={notification.id}, "
|
||||
f"recipient={notification.recipient.id}"
|
||||
)
|
||||
@ -337,7 +364,7 @@ class NotificationService:
|
||||
|
||||
group_name = f'notifications_{notification.recipient.id}'
|
||||
logger.info(
|
||||
f"[NotificationService.send_web_notification] Отправка в группу: {group_name}"
|
||||
f"[NotificationService.send_web_socket] Отправка в группу: {group_name}"
|
||||
)
|
||||
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
@ -349,7 +376,7 @@ class NotificationService:
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[NotificationService.send_web_notification] ✅ Уведомление отправлено в WebSocket"
|
||||
f"[NotificationService.send_web_socket] ✅ Уведомление отправлено в WebSocket"
|
||||
)
|
||||
|
||||
# Обновить счетчик
|
||||
@ -360,7 +387,7 @@ class NotificationService:
|
||||
).count()
|
||||
|
||||
logger.info(
|
||||
f"[NotificationService.send_web_notification] Обновление счетчика: {unread_count} непрочитанных"
|
||||
f"[NotificationService.send_web_socket] Обновление счетчика: {unread_count} непрочитанных"
|
||||
)
|
||||
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
@ -372,11 +399,8 @@ class NotificationService:
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"[NotificationService.send_web_notification] ✅ Счетчик обновлен"
|
||||
f"[NotificationService.send_web_socket] ✅ Счетчик обновлен"
|
||||
)
|
||||
|
||||
# Также отправляем Web Push для offline уведомлений
|
||||
NotificationService.send_web_push_notification(notification)
|
||||
|
||||
@staticmethod
|
||||
def send_web_push_notification(notification: Notification) -> int:
|
||||
@ -450,10 +474,6 @@ class NotificationService:
|
||||
)
|
||||
return 0
|
||||
|
||||
vapid_claims = {
|
||||
'sub': f'mailto:{vapid_email}'
|
||||
}
|
||||
|
||||
success_count = 0
|
||||
failed_subscriptions = []
|
||||
|
||||
@ -467,25 +487,29 @@ class NotificationService:
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
f"[NotificationService.send_web_push_notification] "
|
||||
f"Отправка на endpoint: {subscription.endpoint[:50]}..."
|
||||
)
|
||||
# Извлекаем origin из endpoint для поля 'aud' в VAPID claims
|
||||
from urllib.parse import urlparse
|
||||
parsed_url = urlparse(subscription.endpoint)
|
||||
audience = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||
|
||||
vapid_claims = {
|
||||
'sub': f'mailto:{vapid_email}',
|
||||
'aud': audience
|
||||
}
|
||||
|
||||
webpush(
|
||||
subscription_info=subscription_info,
|
||||
data=json.dumps(payload),
|
||||
vapid_private_key=vapid_private_key,
|
||||
vapid_claims=vapid_claims
|
||||
vapid_claims=vapid_claims,
|
||||
ttl=86400 # 24 часа
|
||||
)
|
||||
|
||||
success_count += 1
|
||||
subscription.reset_errors()
|
||||
|
||||
except WebPushException as e:
|
||||
logger.warning(
|
||||
f"[NotificationService.send_web_push_notification] ⚠️ WebPush ошибка: {e}"
|
||||
)
|
||||
logger.warning(f"[WebPush] Error: {e}")
|
||||
|
||||
# Обрабатываем ошибки
|
||||
subscription.error_count += 1
|
||||
@ -494,26 +518,17 @@ class NotificationService:
|
||||
# Если подписка устарела (410 Gone) или недействительна - деактивируем
|
||||
if e.response and e.response.status_code in [404, 410]:
|
||||
subscription.is_active = False
|
||||
logger.info(
|
||||
f"[NotificationService.send_web_push_notification] "
|
||||
f"Подписка деактивирована: {subscription.id}"
|
||||
)
|
||||
logger.info(f"[WebPush] Subscription {subscription.id} deactivated (expired)")
|
||||
|
||||
# Если слишком много ошибок - деактивируем
|
||||
if subscription.error_count >= 5:
|
||||
subscription.is_active = False
|
||||
logger.info(
|
||||
f"[NotificationService.send_web_push_notification] "
|
||||
f"Подписка деактивирована (много ошибок): {subscription.id}"
|
||||
)
|
||||
logger.info(f"[WebPush] Subscription {subscription.id} deactivated (too many errors)")
|
||||
|
||||
subscription.save(update_fields=['error_count', 'last_error', 'is_active', 'updated_at'])
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"[NotificationService.send_web_push_notification] ❌ Ошибка: {e}",
|
||||
exc_info=True
|
||||
)
|
||||
logger.error(f"[WebPush] Unexpected error: {e}")
|
||||
|
||||
logger.info(
|
||||
f"[NotificationService.send_web_push_notification] ✅ Завершено: "
|
||||
|
||||
@ -74,17 +74,29 @@ class PushNotificationsManager {
|
||||
* @private
|
||||
*/
|
||||
async _fetchVapidKey() {
|
||||
console.log('[PushNotifications][DEBUG] 🔍 Запрос VAPID ключа с сервера...');
|
||||
|
||||
const response = await fetch('/api/v1/notifications/push/vapid-key/', {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
console.log('[PushNotifications][DEBUG] 📡 Ответ сервера:', response.status, response.statusText);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Не удалось получить VAPID ключ');
|
||||
throw new Error(`Не удалось получить VAPID ключ: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('[PushNotifications][DEBUG] 📦 Данные от сервера:', data);
|
||||
|
||||
this.vapidPublicKey = data.vapid_public_key;
|
||||
console.log('[PushNotifications] VAPID ключ получен');
|
||||
|
||||
if (!this.vapidPublicKey) {
|
||||
throw new Error('VAPID ключ отсутствует в ответе сервера');
|
||||
}
|
||||
|
||||
console.log('[PushNotifications][DEBUG] ✅ VAPID ключ получен, длина:', this.vapidPublicKey.length);
|
||||
console.log('[PushNotifications][DEBUG] 🔑 VAPID ключ:', this.vapidPublicKey.substring(0, 20) + '...');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,7 +132,39 @@ class PushNotificationsManager {
|
||||
this.isSubscribed = subscription !== null;
|
||||
|
||||
if (this.isSubscribed) {
|
||||
console.log('[PushNotifications] Активная подписка найдена');
|
||||
// Проверяем, нужна ли синхронизация с сервером
|
||||
const lastEndpoint = localStorage.getItem('push_endpoint');
|
||||
const currentEndpoint = subscription.endpoint;
|
||||
|
||||
// Отправляем на сервер только если endpoint изменился или не сохранён
|
||||
if (lastEndpoint !== currentEndpoint) {
|
||||
try {
|
||||
await this._sendSubscriptionToServer(subscription);
|
||||
localStorage.setItem('push_endpoint', currentEndpoint);
|
||||
} catch (error) {
|
||||
console.warn('[PushNotifications] Ошибка синхронизации, переподписываемся:', error);
|
||||
|
||||
// Если ошибка - возможно VAPID ключи изменились
|
||||
// Удаляем старую подписку и создаём новую
|
||||
try {
|
||||
await subscription.unsubscribe();
|
||||
console.log('[PushNotifications] Старая подписка удалена');
|
||||
|
||||
// Создаём новую подписку с новыми VAPID ключами
|
||||
const newSubscription = await this.swRegistration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: this._urlBase64ToUint8Array(this.vapidPublicKey)
|
||||
});
|
||||
|
||||
await this._sendSubscriptionToServer(newSubscription);
|
||||
localStorage.setItem('push_endpoint', newSubscription.endpoint);
|
||||
console.log('[PushNotifications] ✅ Автоматически переподписались');
|
||||
this.isSubscribed = true;
|
||||
} catch (resubscribeError) {
|
||||
console.error('[PushNotifications] Ошибка переподписки:', resubscribeError);
|
||||
this.isSubscribed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,18 +175,33 @@ class PushNotificationsManager {
|
||||
* @private
|
||||
*/
|
||||
_urlBase64ToUint8Array(base64String) {
|
||||
console.log('[PushNotifications][DEBUG] 🔧 _urlBase64ToUint8Array вход:', {
|
||||
length: base64String.length,
|
||||
first20: base64String.substring(0, 20),
|
||||
last20: base64String.substring(base64String.length - 20)
|
||||
});
|
||||
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
console.log('[PushNotifications][DEBUG] 🔧 Добавлено паддинга:', padding.length);
|
||||
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
console.log('[PushNotifications][DEBUG] 🔧 После замены символов, длина:', base64.length);
|
||||
|
||||
const rawData = window.atob(base64);
|
||||
console.log('[PushNotifications][DEBUG] 🔧 После atob, длина:', rawData.length);
|
||||
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
|
||||
console.log('[PushNotifications][DEBUG] 🔧 Uint8Array готов, длина:', outputArray.length);
|
||||
console.log('[PushNotifications][DEBUG] 🔧 Первые 10 байт:', Array.from(outputArray.slice(0, 10)));
|
||||
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
@ -165,41 +224,94 @@ class PushNotificationsManager {
|
||||
* @returns {Promise<boolean>} Успешность подписки
|
||||
*/
|
||||
async subscribe() {
|
||||
console.log('[PushNotifications][DEBUG] 🚀 Начало процесса подписки');
|
||||
|
||||
if (!this.isSupported || !this.swRegistration || !this.vapidPublicKey) {
|
||||
console.error('[PushNotifications] Менеджер не инициализирован');
|
||||
console.error('[PushNotifications][DEBUG] ❌ Менеджер не инициализирован:', {
|
||||
isSupported: this.isSupported,
|
||||
swRegistration: !!this.swRegistration,
|
||||
vapidPublicKey: !!this.vapidPublicKey
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверяем разрешение
|
||||
console.log('[PushNotifications][DEBUG] 🔐 Проверка разрешений, текущее:', Notification.permission);
|
||||
|
||||
if (Notification.permission === 'denied') {
|
||||
console.warn('[PushNotifications] Уведомления заблокированы пользователем');
|
||||
console.warn('[PushNotifications][DEBUG] ❌ Уведомления заблокированы пользователем');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Notification.permission === 'default') {
|
||||
console.log('[PushNotifications][DEBUG] ❓ Запрос разрешения у пользователя...');
|
||||
const permission = await this.requestPermission();
|
||||
if (permission !== 'granted') {
|
||||
console.log('[PushNotifications][DEBUG] ❌ Разрешение не получено:', permission);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Удаляем старую подписку, если есть (на случай смены VAPID ключей)
|
||||
const existingSubscription = await this.swRegistration.pushManager.getSubscription();
|
||||
console.log('[PushNotifications][DEBUG] 🔍 Существующая подписка:', existingSubscription ? 'найдена' : 'отсутствует');
|
||||
|
||||
if (existingSubscription) {
|
||||
console.log('[PushNotifications][DEBUG] 🗑️ Удаляем старую подписку:', existingSubscription.endpoint.substring(0, 50) + '...');
|
||||
await existingSubscription.unsubscribe();
|
||||
console.log('[PushNotifications][DEBUG] ✅ Старая подписка удалена');
|
||||
}
|
||||
|
||||
// Конвертируем VAPID ключ
|
||||
console.log('[PushNotifications][DEBUG] 🔄 Конвертация VAPID ключа в Uint8Array...');
|
||||
const applicationServerKey = this._urlBase64ToUint8Array(this.vapidPublicKey);
|
||||
console.log('[PushNotifications][DEBUG] ✅ Uint8Array создан, длина:', applicationServerKey.length);
|
||||
console.log('[PushNotifications][DEBUG] 🔢 Первые байты:', Array.from(applicationServerKey.slice(0, 5)));
|
||||
|
||||
// Создаем push-подписку
|
||||
const subscription = await this.swRegistration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: this._urlBase64ToUint8Array(this.vapidPublicKey)
|
||||
console.log('[PushNotifications][DEBUG] 📝 Создание новой подписки...');
|
||||
console.log('[PushNotifications][DEBUG] 🌐 Push Manager:', this.swRegistration.pushManager);
|
||||
|
||||
// Проверяем поддерживаемые функции
|
||||
const pushManager = this.swRegistration.pushManager;
|
||||
console.log('[PushNotifications][DEBUG] 🔍 Push Manager возможности:', {
|
||||
hasGetSubscription: typeof pushManager.getSubscription === 'function',
|
||||
hasSubscribe: typeof pushManager.subscribe === 'function',
|
||||
hasPermissionState: typeof pushManager.permissionState === 'function'
|
||||
});
|
||||
|
||||
console.log('[PushNotifications] Push-подписка создана:', subscription.endpoint);
|
||||
const subscription = await this.swRegistration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: applicationServerKey
|
||||
});
|
||||
|
||||
console.log('[PushNotifications][DEBUG] ✅ Push-подписка создана успешно!');
|
||||
console.log('[PushNotifications][DEBUG] 🌐 Endpoint:', subscription.endpoint.substring(0, 70) + '...');
|
||||
console.log('[PushNotifications][DEBUG] 🔑 Keys:', Object.keys(subscription.toJSON().keys));
|
||||
|
||||
// Отправляем подписку на сервер
|
||||
console.log('[PushNotifications][DEBUG] 📤 Отправка подписки на сервер...');
|
||||
await this._sendSubscriptionToServer(subscription);
|
||||
|
||||
this.isSubscribed = true;
|
||||
console.log('[PushNotifications][DEBUG] 🎉 Подписка завершена успешно!');
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[PushNotifications] Ошибка подписки:', error);
|
||||
console.error('[PushNotifications][DEBUG] ❌ ОШИБКА ПОДПИСКИ:', error);
|
||||
console.error('[PushNotifications][DEBUG] 📋 Тип ошибки:', error.name);
|
||||
console.error('[PushNotifications][DEBUG] 💬 Сообщение:', error.message);
|
||||
console.error('[PushNotifications][DEBUG] 📚 Stack:', error.stack);
|
||||
|
||||
// Дополнительная диагностика
|
||||
if (error.name === 'AbortError') {
|
||||
console.error('[PushNotifications][DEBUG] 🔍 AbortError обычно означает:');
|
||||
console.error('[PushNotifications][DEBUG] 1. Невалидный VAPID ключ');
|
||||
console.error('[PushNotifications][DEBUG] 2. Проблема с push-сервисом (FCM/Mozilla)');
|
||||
console.error('[PushNotifications][DEBUG] 3. Неправильный формат applicationServerKey');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ const SW_VERSION = '1.0.0';
|
||||
// Настройки по умолчанию для уведомлений
|
||||
const DEFAULT_NOTIFICATION_OPTIONS = {
|
||||
icon: '/static/img/logo-192.png',
|
||||
badge: '/static/img/badge-72.png',
|
||||
vibrate: [100, 50, 100],
|
||||
requireInteraction: false,
|
||||
renotify: true,
|
||||
@ -55,7 +54,8 @@ self.addEventListener('activate', (event) => {
|
||||
* }
|
||||
*/
|
||||
self.addEventListener('push', (event) => {
|
||||
console.log('[SW] Push received:', event);
|
||||
console.log('[SW] 🔔 Push received:', event);
|
||||
console.log('[SW] 🔔 Has data:', !!event.data);
|
||||
|
||||
let notificationData = {
|
||||
title: 'Новое уведомление',
|
||||
@ -67,7 +67,7 @@ self.addEventListener('push', (event) => {
|
||||
if (event.data) {
|
||||
try {
|
||||
const payload = event.data.json();
|
||||
console.log('[SW] Push payload:', payload);
|
||||
console.log('[SW] 🔔 Push payload:', payload);
|
||||
|
||||
notificationData = {
|
||||
...notificationData,
|
||||
@ -97,6 +97,8 @@ self.addEventListener('push', (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[SW] 🔔 Showing notification:', notificationData.title);
|
||||
|
||||
// Показываем уведомление
|
||||
const promiseChain = self.registration.showNotification(
|
||||
notificationData.title,
|
||||
@ -112,7 +114,11 @@ self.addEventListener('push', (event) => {
|
||||
image: notificationData.image,
|
||||
actions: notificationData.actions,
|
||||
}
|
||||
);
|
||||
).then(() => {
|
||||
console.log('[SW] ✅ Notification shown successfully:', notificationData.title);
|
||||
}).catch((error) => {
|
||||
console.error('[SW] ❌ Failed to show notification:', error);
|
||||
});
|
||||
|
||||
event.waitUntil(promiseChain);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user